diff --git a/, b/, deleted file mode 100755 index a59571f..0000000 --- a/, +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash -# -# usage example: -# $ , yarn --help -# This finds a derivation providing a bin/yarn, and runs it with `nix run`. -# If there are multiple candidates, the user chooses one using `fzy`. -set -euo pipefail - -picker=fzy -if [ -n "${COMMA_PICKER+1}" ]; then - picker="${COMMA_PICKER}" -fi - -# Test that the picker exists -if ! [ -x "$(command -v "${picker}")" ]; then - >&2 echo "picker ${picker} is not found in PATH" - exit 1 -fi - -if [[ $# -lt 1 ]]; then - >&2 echo "usage: , [arguments]" - exit 1 -fi - -if [[ "$1" == "--install" ]] || [[ "$1" == "-i" ]]; then - install=1 - shift -else - install="" -fi - -argv0=$1; shift - -case "${argv0}" in - @OVERLAY_PACKAGES@) - attr="${argv0}" - ;; - *) - attr="$(nix-locate --top-level --minimal --at-root --whole-name "/bin/${argv0}")" - if [[ "$(echo "${attr}" | wc -l)" -ne 1 ]]; then - attr="$(echo "${attr}" | "${picker}")" - fi - ;; -esac - -if [[ -z $attr ]]; then - >&2 echo "no match" - exit 1 -fi - -# on flake based installations nixpkgs is specified via -# flake input and therefore NIX_PATH might be unset -if echo $NIX_PATH | grep -q "nixpkgs="; then - nixArgs="" -else - nixArgs="-I nixpkgs=${NIXPKGS}" -fi - -if [[ -n $install ]]; then - nix-env $nixArgs -iA "nixpkgs.${attr%%.*}" -else - nix_version_greater_or_equal() { - local nix_version - nix_version=$(nix --version | cut -f3 -d ' ') - printf '%s\n%s' "$1" "$nix_version" | sort -C -V - } - - if nix_version_greater_or_equal "2.4"; then - nix --extra-experimental-features 'nix-command flakes' shell "${NIXPKGS}#${attr}" -c "${argv0}" "$@" - else - nix run "nixpkgs.${attr}" -c "${argv0}" "$@" - fi -fi diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 75c248f..e8bafbc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,14 @@ # ignore nix-build result folder result + + +# Added by cargo + +/target + + +# Added by cargo +# +# already existing elements were commented out + +#/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..48b4578 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,146 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "clap" +version = "3.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aad2534fad53df1cc12519c5cda696dd3e20e6118a027e24054aea14a0bdcbe" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "comma" +version = "0.1.0" +dependencies = [ + "clap", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..226dc5f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "comma" +description = "runs programs without installing them" +version = "0.1.0" +edition = "2021" +authors = ["Artturin "] +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "3.1.9" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 5eccde2..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2020 Shopify - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/README.md b/README.md index 849d656..85c7e56 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,30 @@ -# , +# comma Comma runs software without installing it. -Basically it just wraps together `nix run` and `nix-index`. You stick a `,` in front of a command to +Basically it just wraps together `nix shell -c` and `nix-index`. You stick a `,` in front of a command to run it from whatever location it happens to occupy in `nixpkgs` without really thinking about it. ## Installation -```bash -nix-env -i -f . -``` +- Nix with [Flakes](https://nixos.wiki/wiki/Flakes): + + ```bash + $ nix profile install github:nix-community/comma + ``` + +- No flakes: + + ```bash + $ nix-env -i -f "https://github.com/nix-community/comma/archive/master.tar.gz" + ``` ## Usage -[See a quick demo on -YouTube](https://www.youtube.com/watch?v=VUM3Km_4gUg&list=PLRGI9KQ3_HP_OFRG6R-p4iFgMSK1t5BHs) - ```bash , cowsay neato ``` - ## Prebuilt index https://github.com/Mic92/nix-index-database diff --git a/default.nix b/default.nix index 5fe9a69..2cccff2 100644 --- a/default.nix +++ b/default.nix @@ -1,35 +1,10 @@ -{ pkgs ? import { } - -, stdenv ? pkgs.stdenv -, lib ? pkgs.lib -, fetchurl ? pkgs.fetchurl -, nix ? pkgs.nix -, fzy ? pkgs.fzy -, makeWrapper ? pkgs.makeWrapper - -# We use this to add matchers for stuff that's not in upstream nixpkgs, but is -# in our own overlay. No fuzzy matching from multiple options here, it's just: -# Was the command `, mything`? Run `nixpkgs.mything`. -, overlayPackages ? [] -}: - -stdenv.mkDerivation rec { - name = "comma"; - - src = ./.; - - nativeBuildInputs = [ makeWrapper ]; - - installPhase = let - caseCondition = lib.concatStringsSep "|" (overlayPackages ++ [ "--placeholder--" ]); - in '' - mkdir -p $out/bin - sed -e 's/@OVERLAY_PACKAGES@/${caseCondition}/' < , > $out/bin/, - chmod +x $out/bin/, - wrapProgram $out/bin/, \ - --prefix PATH : ${nix}/bin \ - --prefix PATH : ${fzy}/bin - - ln -s $out/bin/, $out/bin/comma - ''; -} +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).defaultNix diff --git a/flake.lock b/flake.lock index 9439c0f..53f4661 100644 --- a/flake.lock +++ b/flake.lock @@ -1,23 +1,79 @@ { "nodes": { - "nixpkgs": { + "flake-compat": { + "flake": false, "locked": { - "lastModified": 1638110343, - "narHash": "sha256-hQaow8sGPyUrXgrqgDRsfA+73uR0vms2goTQNxIAaRQ=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "942eb9a335b4cd22fa6a7be31c494e53e76f5637", + "lastModified": 1648199409, + "narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "64a525ee38886ab9028e6f61790de0832aa3ef03", "type": "github" }, "original": { - "id": "nixpkgs", - "ref": "nixos-unstable", - "type": "indirect" + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1650101877, + "narHash": "sha256-IYxYc5Z1ZHQDTQbTWsOZ9HlZepsVjgV5oZwmbKzxSJs=", + "owner": "nix-community", + "repo": "naersk", + "rev": "8cc379478819e6a22ce7595a761fe1e17c8d7458", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "master", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1650109093, + "narHash": "sha256-tqlnKrAdJktRLXTou9le0oTqrYBAFpGscV5RADdpArU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "faad370edcb37162401be50d45526f52bb16a713", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" } }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "flake-compat": "flake-compat", + "naersk": "naersk", + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "utils": { + "locked": { + "lastModified": 1649676176, + "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 01bb93f..0a26759 100644 --- a/flake.nix +++ b/flake.nix @@ -1,28 +1,50 @@ { - description = "Comma runs software without installing it"; + description = "runs programs without installing them"; inputs = { - nixpkgs.url = "nixpkgs/nixos-unstable"; + naersk = { + url = "github:nix-community/naersk/master"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + utils.url = "github:numtide/flake-utils"; + flake-compat = { + url = github:edolstra/flake-compat; + flake = false; + }; }; - outputs = { self, nixpkgs, }: - let - b = builtins; - lib = nixpkgs.lib; - supportedSystems = lib.systems.supported.hydra; - forAllSystems = f: lib.genAttrs supportedSystems - (system: f system (import nixpkgs { inherit system; })); - in - rec { + outputs = { self, nixpkgs, utils, naersk, flake-compat }: + utils.lib.eachDefaultSystem (system: + let + inherit (nixpkgs) lib; + pkgs = nixpkgs.legacyPackages.${system}; + naersk-lib = pkgs.callPackage naersk { }; + in + { + packages = { + default = self.packages."${system}".comma; + comma = naersk-lib.buildPackage { + pname = "comma"; + root = ./.; + nativeBuildInputs = with pkgs; [ makeWrapper ]; + overrideMain = _: { + postInstall = '' + wrapProgram $out/bin/comma \ + --prefix PATH : ${lib.makeBinPath (with pkgs; [ nix fzy ])} + ln -s $out/bin/comma $out/bin/, + ''; + }; + }; + }; - packages = forAllSystems - (system: pkgs: { - comma = import ./default.nix { - inherit pkgs; + apps.default = utils.lib.mkApp { + drv = self.packages."${system}".default; + }; + + devShells.default = with pkgs; mkShell { + nativeBuildInputs = [ cargo rustc rustfmt rustPackages.clippy fzy ]; + RUST_SRC_PATH = rustPlatform.rustLibSrc; }; }); - - defaultPackage = forAllSystems (system: pkgs: packages."${system}".comma); - - }; } diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..6234bb4 --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +(import + ( + let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { src = ./.; } +).shellNix diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..79e62a0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,115 @@ +use std::{ + env, + io::Write, + os::unix::prelude::CommandExt, + process::{exit, Command, Stdio}, +}; + +use clap::{arg, Arg}; + +fn pick(picker: &str, derivations: Vec<&str>) -> String { + let mut picker_process = Command::new(&picker) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap_or_else(|err| panic!("failed to execute {}: {}", picker, err)); + + let picker_stdin = picker_process.stdin.as_mut().unwrap(); + + picker_stdin + .write_all(derivations.join("\n").as_bytes()) + .expect("failure to write stdin"); + + let output = picker_process.wait_with_output().unwrap().stdout; + + if output.is_empty() { + exit(1) + } + std::str::from_utf8(&output) + .expect("fail") + .trim() + .to_string() +} + +fn run_command(use_channel: bool, choice: &str, command: &str, trail: Vec<&str>) { + let mut run_cmd = Command::new("nix"); + + run_cmd.args([ + "--extra-experimental-features", + "nix-command flakes", + "shell", + ]); + + if use_channel { + run_cmd.args(["-f", "", choice]); + } else { + run_cmd.args([format!("nixpkgs#{}", choice)]); + } + + run_cmd.args(["--command", command]); + run_cmd.args(trail); + run_cmd.exec(); +} + +fn main() { + let matches = clap::Command::new("comma") + .about("runs programs without installing them") + .arg( + Arg::new("install") + .short('i') + .long("install") + .takes_value(false) + .help("install the derivation containing the executable"), + ) + .trailing_var_arg(true) + .arg(arg!( ... "command to run")) + .get_matches(); + + let picker = match env::var("COMMA_PICKER") { + Ok(val) => val, + Err(_) => "fzy".to_string(), + }; + + let install = matches.is_present("install"); + + let mut trail: Vec<&str> = matches.values_of("cmd").unwrap().collect(); + let command: String = trail.remove(0).to_string(); + + let attrs = Command::new("nix-locate") + .args(["--top-level", "--minimal", "--at-root", "--whole-name"]) + .arg(format!("/bin/{}", command)) + .output() + .expect("failed to execute nix-locate") + .stdout; + + if attrs.is_empty() { + eprintln!("no match"); + std::process::exit(1) + } + + let attrs: Vec<&str> = std::str::from_utf8(&attrs) + .expect("fail") + .trim() + .split('\n') + .collect(); + + let choice = if attrs.len() != 1 { + pick(&picker, attrs) + } else { + attrs.first().unwrap().trim().to_string() + }; + + let use_channel = (match env::var("NIX_PATH") { + Ok(val) => val, + Err(_) => "".to_string(), + }) + .contains("nixpkgs"); + + if install { + Command::new("nix-env") + .args(["-f", "", "-iA", choice.rsplit('.').last().unwrap()]) + .exec(); + } else { + run_command(use_channel, &choice, &command, trail) + } +}