diff --git a/README.md b/README.md index 3515d608..d045d857 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,64 @@ # nu-git-manager A collection of Nushell tools to manage `git` repositories. -## :bulb: what is `nu-git-manager` +# Table of content +- [*what is `nu-git-manager`*](#bulb-what-is-nu-git-manager-toc) +- [*requirements*](#link-requirements-toc) +- [*installation*](#recycle-installation-toc) +- [*usage*](#gear-usage-toc) + - [*getting help*](#pray-getting-help-toc) +- [*some ideas of advanced (?) usage*](#exclamation-some-ideas-of-advanced--usage-toc) + +## :bulb: what is `nu-git-manager` [[toc](#table-of-content)] like [`ghq`](https://github.com/x-motemen/ghq), `nu-git-manager` aims at being a fully-featured repository manager, purely written in Nushell. -the public API of `nu-git-manager` is greatly inspired by `ghq` for now but this might very likely change -in the future! - -regarding versions, as `nu-git-manager` is tied to the version of the main `nushell/nushell` repo, -its versioning cycle will be the same -- a new minor release every 3 weeks -- starting may 2023 tuesday the 16th - -more information can be found in the [documentation](docs/)! +it provides two main modules: +- `nu-git-manager` itself which ships the main `gm` command +- `nu-git-manager sugar` which exports a bunch of Git-related tools, e.g. to help use the `gh` command or augment the capabilities of `git` -## :link: requirements -- Nushell 0.80.1+ -- `git` 2.40.1 +## :link: requirements [[toc](#table-of-content)] +- [Nushell] 0.85.1+ + - with Cargo and `cargo install nu` +- `git` 2.34.1 + - with Pacman and `pacman -S extra/git` + - with Nix and `nix run nixpkgs#git` - `gh` (optional) 2.29.0 (used by `sugar gh`) + - with Pacman and `pacman -S community/github-cli` + - with Nix and `nix run nixpkgs#gh` +- `find` 4.9.0 + - with Pacman and `pacman -S core/findutils` + - with Nix and `nix run nixpkgs#findutils` -## :recycle: installation [here](docs/installation/) +## :recycle: installation [[toc](#table-of-content)] +- install [Nupm] (**recommended**) by following the [Nupm instructions] +- download the `nu-git-manager` repository +```shell +git clone https://github.com/amtoine/nu-git-manager +``` +- activate the `nupm` module with `use nupm` +- install the `nu-git-manager` package +```nushell +nupm install --path --force nu-git-manager +``` -## :gear: usage +## :gear: usage [[toc](#table-of-content)] in your `config.nu` you can add the following to load `nu-git-manager` modules: ```nu # config.nu -# load the main `gm` module -use nu-git-manager gm +# load the main `gm` command +use nu-git-manager [gm, "gm clone", "gm list", "gm root", "gm remove"] # the following are non-essential modules -use nu-git-manager sugar git # load `git` tool extensions +use nu-git-manager sugar git # augmnet Git with custom commands use nu-git-manager sugar gh # load commands to interact with *GitHub* use nu-git-manager sugar gist # load commands to interact with *GitHub* gists -use nu-git-manager sugar completions git * # load some `git` completion -use nu-git-manager sugar dotfiles # load tools to manage versionned dotfiles ``` then you have access to the whole `nu-git-manager` suite :partying_face: -### :pray: getting help +### :pray: getting help [[toc](#table-of-content)] do not hesitate to run one of the following to have more information about what `nu-git-manager` has to offer :thumbsup: ```nu help gm @@ -59,42 +77,32 @@ help modules gist gist ``` -## :exclamation: some ideas of advanced (?) usage -one thing i like to do in my config to go ***BLAZZINGLY FAST*** is to use keybindings to call some `nu-git-manager` commands -in one key stroke :smirk: +## :exclamation: some ideas of advanced (?) usage [[toc](#table-of-content)] +everytime i open a terminal, i use [Tmux] to manage sessions, switch between them, detach and reattach, quite a ***BLAZZINGLY FAST*** workflow for my taste :smirk: -- with `gm` activated, i can jump to any repo from anywhere with `` -```nu -{ - name: open_repo - modifier: control - keycode: char_g - mode: [emacs, vi_insert, vi_normal] - event: { - send: executehostcommand - cmd: "gm goto" - } -} -``` -- with `sugar dotfiles` activated, i can edit any configuration file from anywhere with `` -```nu -{ - name: edit_config - modifier: control - keycode: char_v - mode: [emacs, vi_insert, vi_normal] - event: { - send: executehostcommand - cmd: "dotfiles edit" - } -} +to achieve this, i use the [`tmux-sessionizer.nu` script][`tmux-sessionizer.nu`] from the [`nu-goat-scripts` package][`nu-goat-scripts`], again installed with [Nupm] :ok_hand: + +then, in my Tmux config, i have a binding to +1. list all my Git repositories +2. fuzzy-pick one of them with the [`main` command of `tmux-sessionizer.nu`][`tmux-sessionizer.nu`] +3. create or reattach to the session associated with the repository +```bash +# ~/.config/tmux/tmux.conf + +NUPM_HOME="~/.local/share/nupm" +TMUX_SESSIONIZER="$NUPM_HOME/scripts/tmux-sessionizer.nu" + +bind-key -r t display-popup -E "nu --commands ' + use $NUPM_HOME/modules/nu-git-manager *;\ + $TMUX_SESSIONIZER (gm list --full-path) --short\ +'" ``` -## :calendar: the roadmap of `nu-git-manager` -- [ ] complete the main `gm` commands -- [ ] support more hosts, e.g. *GitLab* -- [ ] add more external completions, namely for `git` and `gh`, maybe `glab` +[Nushell]: https://github.com/nushell/nushell + +[Nupm]: https://github.com/nushell/nupm +[Nupm instructions]: https://github.com/nushell/nupm#-installation -[nushell/nushell#9066]: https://github.com/nushell/nushell/pull/9066 -[`a2a346e39`]: https://github.com/nushell/nushell/commit/a2a346e39c53e386b97d8d7f9a05ed58298e8789 -[#21]: https://github.com/amtoine/nu-git-manager/pull/21 +[Tmux]: https://github.com/tmux/tmux +[`tmux-sessionizer.nu`]: https://github.com/goatfiles/scripts/blob/main/nu_scripts/scripts/tmux-sessionizer.nu#L463 +[`nu-goat-scripts`]: https://github.com/goatfiles/scripts/blob/main/nu_scripts/README.md#nu_scripts diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 1ee395d1..00000000 --- a/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## official documentation of `nu-git-manager` - -- :recycle: install `nu-git-manager` [here](installation/) diff --git a/docs/installation/README.md b/docs/installation/README.md deleted file mode 100644 index 0ef9248f..00000000 --- a/docs/installation/README.md +++ /dev/null @@ -1,7 +0,0 @@ -> **Warning** -> make sure you have the dependencies installed as specified in -> [`packages.nuon`](../../package.nuon) - -currently, there are two ways to install `nu-git-manager` -- the [manual process](manual.md) -- with the [`nupm` package manager](nupm.md) diff --git a/docs/installation/manual.md b/docs/installation/manual.md deleted file mode 100644 index 3be56577..00000000 --- a/docs/installation/manual.md +++ /dev/null @@ -1,22 +0,0 @@ -one way to install `nu-git-manager` right now is the following manual process - -> **Note** -> let's say you have defined the following environment variable -> ```nu -> # in `$nu.env-path` -> $env.NU_LIB_PATH = "/path/to/libs" -> ``` - -- clone the repo to a location you want it to be -```nu -git clone https://github.com/amtoine/nu-git-manager.git ($env.NU_LIB_PATH | path join "nu-git-manager") -``` -- make it loadable in your `NU_LIB_DIRS` -```nu -# in `$nu.env-path` -$env.NU_LIB_DIRS = ($env.NU_LIB_DIRS | append $env.NU_LIB_PATH) -``` -- update it regularly to have the latest version -```nu -git -C ($env.NU_LIB_PATH | path join "nu-git-manager") pull -``` diff --git a/docs/installation/nupm.md b/docs/installation/nupm.md deleted file mode 100644 index 911607a1..00000000 --- a/docs/installation/nupm.md +++ /dev/null @@ -1,16 +0,0 @@ -i like to use the `nupm` package manager for Nushell :yum: - -- first install the package manager [here](https://github.com/amtoine/nupm/tree/main#recycle-installation) -- then install `nu-git-manager` with a simple -```nu -nupm install https://github.com/amtoine/nu-git-manager.git -``` -- finally you can activate commands and modules with something like -```nu -nupm activate nu-git-manager gm -nupm activate nu-git-manager sugar git -nupm activate nu-git-manager sugar gh -nupm activate nu-git-manager sugar gist -nupm activate nu-git-manager sugar completions git * -nupm activate nu-git-manager sugar dotfiles -``` diff --git a/nu-git-manager/fs/dir.nu b/nu-git-manager/fs/dir.nu new file mode 100644 index 00000000..dade386d --- /dev/null +++ b/nu-git-manager/fs/dir.nu @@ -0,0 +1,10 @@ +# Cross platform wrapper to open a directory, a file or a URL in the default application +export def open-item [file: path]: nothing -> nothing { + let cmd = match $nu.os-info.name { + "windows" => "explorer", + "macos" => "open", + "linux" => "xdg-open" + } + + ^$cmd $file +} diff --git a/nu-git-manager/fs/path.nu b/nu-git-manager/fs/path.nu new file mode 100644 index 00000000..48a8007e --- /dev/null +++ b/nu-git-manager/fs/path.nu @@ -0,0 +1,4 @@ +# sanitize a Windows path +export def "path sanitize" []: path -> path { + str replace --all '\' '/' +} diff --git a/nu-git-manager/fs/store.nu b/nu-git-manager/fs/store.nu new file mode 100644 index 00000000..3ca15f10 --- /dev/null +++ b/nu-git-manager/fs/store.nu @@ -0,0 +1,22 @@ +use path.nu "path sanitize" + +export def get-repo-store-path []: nothing -> path { + $env.GIT_REPOS_HOME? | default ( + $env.XDG_DATA_HOME? | default ($nu.home-path | path join ".local/share") | path join "repos" + ) | path expand | path sanitize +} + +export def list-repos-in-store []: nothing -> list { + if not (get-repo-store-path | path exists) { + return [] + } + + if $nu.os-info.name == "windows" { + # FIXME: this is super slow on windows + glob **/*.git --not [**/*.venv **/node_modules/** **/target/** **/build/** */] + } else { + # FIXME: do not use external `find` command + ^find (get-repo-store-path) -name ".git" + | lines + } | each { path split | range 0..(-2) | path join } +} diff --git a/nu-git-manager/git/url.nu b/nu-git-manager/git/url.nu new file mode 100644 index 00000000..8be004ea --- /dev/null +++ b/nu-git-manager/git/url.nu @@ -0,0 +1,66 @@ +use ../fs/path.nu "path sanitize" + +export def parse-git-url []: string -> record { + str replace --regex '^git@(.*):' 'ssh://$1/' + | str replace --regex '\.git$' '' + | url parse + | select host path + | update path { + str trim --left --right --char '/' + | str replace --regex '\/tree\/.*' '' + | path split + | { + owner: ($in | first), + group: ($in | range 1..(-2) | if $in != null { path join | path sanitize }), + repo: ($in | last) + } + } + | flatten + | into record +} + +export def get-fetch-push-urls [ + repository: record, # typically from `parse-git-url` + fetch: string, # one of 'https', 'ssh', or empty + push: string, # one of 'https', 'ssh', or empty + ssh: bool, +]: nothing -> record { + let base_url = { + scheme: null, + host: $repository.host, + path: ( + [$repository.owner $repository.group $repository.repo] + | compact + | path join + | path sanitize + ) + } + let http_url = $base_url | update scheme "https" | url join + let ssh_url = $base_url | update scheme "ssh" | url join + + let fetch_url = match $fetch { + "https" => $http_url, + "ssh" => $ssh_url, + _ => { + if $ssh { + $ssh_url + } else { + $http_url + } + }, + } + + let push_url = match $push { + "https" => $http_url, + "ssh" => $ssh_url, + _ => { + if $ssh { + $ssh_url + } else { + $http_url + } + }, + } + + {fetch: $fetch_url, push: $push_url} +} diff --git a/nu-git-manager/mod.nu b/nu-git-manager/mod.nu index ac0b1bc6..38a396e6 100644 --- a/nu-git-manager/mod.nu +++ b/nu-git-manager/mod.nu @@ -1,2 +1,173 @@ -export module src/gm/ -export module src/sugar/ +use std log + +use fs/store.nu [get-repo-store-path, list-repos-in-store] +use git/url.nu [parse-git-url, get-fetch-push-urls] + +def "nu-complete git-protocols" []: nothing -> table { + [ + [value, description]; + + ["https", "use the HTTP protocol: will require a PAT authentification for private repositories"], + ["ssh", "use the SSH protocol: will require a passphrase unless setup otherwise"], + ] +} + +# manage your Git repositories with the main command of `nu-git-manager` +export def "gm" []: nothing -> nothing { + print (help gm) +} + +# clone a remote Git repository into your local store +# +# will give a nice error if the repository is already in the local store. +# +# # Examples +# clone a repository in the local store of `nu-git-manager` +# > gm clone https://github.com/amtoine/nu-git-manager +# +# clone as a bare repository, i.e. a repo without a worktree +# > gm clone --bare https://github.com/amtoine/nu-git-manager +# +# clone a repo and change the name of the remote +# > gm clone --remote default https://github.com/amtoine/nu-git-manager +# +# setup a public repo in the local store and use HTTP to fetch without PAT and push with SSH +# > gm clone https://github.com/amtoine/nu-git-manager --fetch https --push ssh +export def "gm clone" [ + url: string # the URL to the repository to clone, supports HTTPS and SSH links, as well as references ending in `.git` or starting with `git@` + --remote: string = "origin" # the name of the remote to setup + --ssh # setup the remote to use the SSH protocol both to FETCH and to PUSH + --fetch: string@"nu-complete git-protocols" # setup the FETCH protocol explicitely, will overwrite `--ssh` for FETCH + --push: string@"nu-complete git-protocols" # setup the PUSH protocol explicitely, will overwrite `--ssh` for PUSH + --bare # clone the repository as a "bare" project +]: nothing -> nothing { + let repository = $url | parse-git-url + + let local_path = get-repo-store-path + | append [$repository.host $repository.owner $repository.group $repository.repo] + | compact + | path join + + if ($local_path | path exists) { + let span = metadata $url | get span + error make { + msg: $"(ansi red_bold)repository_already_in_store(ansi reset)" + label: { + text: $"this repository has already been cloned by (ansi {fg: "default_dimmed", attr: "it"})gm(ansi reset)" + start: $span.start + end: $span.end + } + } + } + + let urls = get-fetch-push-urls $repository $fetch $push $ssh + + if $bare { + git clone $urls.fetch $local_path --origin $remote --bare + } else { + git clone $urls.fetch $local_path --origin $remote + } + + git -C $local_path remote set-url $remote $urls.fetch + git -C $local_path remote set-url $remote --push $urls.push +} + +# list all the local repositories in your local store +# +# # Examples +# list all the repositories in the store +# > gm list +# +# list all the repositories in the store with their full paths +# > gm list --full-path +# +# jump to a directory in the store +# > cd (gm list --full-path | input list) +export def "gm list" [ + --full-path # show the full path instead of only the "owner + group + repo" name +]: nothing -> list { + if $full_path { + list-repos-in-store + } else { + let root = get-repo-store-path + list-repos-in-store | each { + str replace $root '' | str trim --left --char (char path_sep) + } + } +} + +# get the root of the local store of repositories managed by `nu-git-manager` +# +# `nu-git-manager` will look for a store in the following places, in order: +# - `$env.GIT_REPOS_HOME` +# - `$env.XDG_DATA_HOME | path join "repos" +# - `~/.local/share/repos` +# +# # Example +# a contrived example, assuming you are in `~` +# > GIT_REPOS_HOME=foo gm root +# ~/foo +export def "gm root" []: nothing -> path { + get-repo-store-path +} + +# remove one of the repositories from your local store +# +# # Examples +# remove any repository by fuzzy-finding the whole store +# > gm remove --fuzzy +# +# restrict the search to any one of my repositories +# > gm remove amtoine +# +# remove a precise repo by giving its full name, a name collision is unlikely +# > gm remove amtoine/nu-git-manager +export def "gm remove" [ + pattern?: string # a pattern to restrict the choices + --fuzzy # remove after fuzzy-finding the repo(s) to clean +]: nothing -> nothing { + let root = get-repo-store-path + let choices = list-repos-in-store + | each { + str replace $root '' | str trim --left --char (char path_sep) + } + | find $pattern + + let repo_to_remove = match ($choices | length) { + 0 => { + let span = metadata $pattern | get span + error make { + msg: $"(ansi red_bold)no_matching_repository(ansi reset)" + label: { + text: $"no repository matching this in (ansi {fg: "default_dimmed", attr: "it"})($root)(ansi reset)" + start: $span.start + end: $span.end + } + } + }, + 1 => { $choices | first }, + _ => { + let prompt = $"please choose a repository to (ansi red)remove(ansi reset)" + let choice = if $fuzzy { + $choices | input list --fuzzy $prompt + } else { + $choices | input list $prompt + } + + if ($choice | is-empty) { + log info "user chose to exit" + return + } + + $choice + }, + } + + let prompt = $"are you (ansi defu)sure(ansi reset) you want to (ansi red_bold)remove(ansi reset) (ansi yellow)($repo_to_remove)(ansi reset)? " + match (["no", "yes"] | input list $prompt) { + "no" => { log info $"user chose to (ansi green_bold)keep(ansi reset) (ansi yellow)($repo_to_remove)(ansi reset)" }, + "yes" => { rm --recursive --force --verbose ($root | path join $repo_to_remove) }, + } + + null +} diff --git a/nu-git-manager/src/gm/mod.nu b/nu-git-manager/src/gm/mod.nu deleted file mode 100644 index d744cb35..00000000 --- a/nu-git-manager/src/gm/mod.nu +++ /dev/null @@ -1,169 +0,0 @@ -use std log -use utils/utils.nu [ - "get root dir" - "parse project" - "default project" - "pick repo" - "list repos" -] - -# fuzzy-jump to any repository managed by `gm` -export def-env goto [ - query?: string # a search query to narrow down the list of choices -] { - let choice = (pick repo - $"Please (ansi yellow_italic)choose a repo(ansi reset) to (ansi green_underline)jump to:(ansi reset)" - $query - ) - if ($choice | is-empty) { - return - } - - cd (get root dir | path join $choice) -} - -# fuzzy-delete any repository managed by `gm` -export def remove [ - query?: string # a search query to narrow down the list of choices - --force (-f) # do not ask for comfirmation when deleting a repository -] { - let choice = (pick repo - $"Please (ansi yellow_italic)choose a repo(ansi reset) to (ansi red_underline)completely remove:(ansi reset)" - $query - ) - if ($choice | is-empty) { - return - } - - let repo = (get root dir | path join $choice) - if $force { - rm --trash --verbose --recursive $repo - } else { - rm --trash --verbose --recursive $repo --interactive - } -} - -# TODO: add support for other hosts than github -# TODO: better worktree support - -# Clone a repository into a standard location -# -# This place is organised by domain and path. -export def grab [ - project: string # |//|/| - --ssh (-p) # use ssh instead of https. - --bare (-b) # clone as *bare* repo (specific to worktrees). - --update (-u) # not supported - --shallow (-s) # not supported - --branch # not supported - --no-recursive # not supported - --look # not supported - --silent # not supported - --vcs (-v): string # not supported -] { - # TODO: implement `--update` option - if $update { - log warning "`--update` option for `gm grab` COMING SOON" - } - # TODO: implement `--shallow` option - if $shallow { - log warning "`--shallow` option for `gm grab` COMING SOON" - } - # TODO: implement `--branch` option - if $branch { - log warning "`--branch` option for `gm grab` COMING SOON" - } - # TODO: implement `--look` option - if $look { - log warning "`--look` option for `gm grab` COMING SOON" - } - # TODO: implement `--silent` option - if $silent { - log warning "`--silent` option for `gm grab` COMING SOON" - } - # TODO: implement `--no-recursive` option - if $no_recursive { - log warning "`--no-recursive` option for `gm grab` COMING SOON" - } - if ($vcs | is-empty) { - log debug "`--vcs` option is NOT SUPPORTED in `gm grab`" - } - - let span = metadata $project | get span - - let project = ( - parse project $project - | default project - | update project { str replace --regex --all '\/' '-'} - ) - - let url = if $ssh { - $"git@($project.host):($project.user)/($project.project).git" - } else { - $"https://($project.host)/($project.user)/($project.project).git" - } - - let local = (get root dir | path join $project.host $project.user $project.project) - - if ($local | path exists) { - error make { - msg: $"(ansi red)repo_already_grabbed(ansi reset)" - label: { - text: "this repo has already been grabbed" - start: $span.start - end: $span.end - } - } - } - - if $bare { - git clone --bare --recurse-submodules $url $local - } else { - git clone --recurse-submodules $url $local - } -} - -# list locally-cloned repositories -# -# by default `gm list` only searches the three first depth levels: -# - host -# - user -# - project -export def list [ - query?: string # return only repositories matching the query - --exact (-e) # force the match to be exact, i.e. the query equals to project, user/project or host/user/project - --full-path (-p) # return the full paths instead of path relative to the `gm` root - --recursive # perform a recursive search of all `.git/` directories -] {( - list repos $query - --exact $exact - --full-path $full_path - --recursive $recursive -)} - -# print the root of the repositories -export def root [ - --all (-a) # not supported -] { - if $all { - log debug "`--all` option is NOT SUPPORTED in `gm root`" - } - - get root dir -} - -# create a new repository -export def create [ - repository: string # |//|/| - --vcs (-v): string # not supported -] { - if ($vcs | is-empty) { - log debug "`--vcs` option is NOT SUPPORTED in `gm create`" - } - - # TODO: implement `gm create` - log warning "COMING SOON" -} - -# the `nu-[g]it-[m]anager`, a WIP to manage any `git` repo in a centralized store, with sugar on top -export def main [] { help gm } diff --git a/nu-git-manager/src/gm/utils/utils.nu b/nu-git-manager/src/gm/utils/utils.nu deleted file mode 100644 index bc7e7b2d..00000000 --- a/nu-git-manager/src/gm/utils/utils.nu +++ /dev/null @@ -1,160 +0,0 @@ -export def "get root dir" [] { - $env.GIT_REPOS_HOME? | default ( - $env.XDG_DATA_HOME? - | default ($env.HOME | path join ".local" "share") - | path join "nu-git-manager" - ) -} - -# Replace all backslashes with forward slashes. -export def "replace slashes" [] { - str replace --all '\' '/' -} - -# parse-project -> record -# parse-project // -> record -# parse-project / -> record -# parse-project -> record -export def "parse project" [ - project: string # |//|/| -] { - let project = ( - $project - | str replace --regex '.git$' '' - | str replace --regex '^http://' '' - | str replace --regex '^https://' '' - | str replace --regex '^ssh://' '' - | str replace --regex '^git@' '' - | str replace --regex --all ':' '/' - | str replace --regex --all '\/+' '/' - | str trim -c '/' - ) - - let hup = ($project | parse "{host}/{user}/{project}") - if not ($hup | is-empty) { - return ($hup | into record) - } - - let up = ($project | parse "{user}/{project}") - if not ($up | is-empty) { - return ($up | into record) - } - - {project: $project} -} - -export def "default project" [] { - default (git config --global user.name) user - | default "github.com" host -} - -export def "list repos" [ - query?: string - --exact: bool = false - --full-path: bool = false - --recursive: bool = false -] { - let root = (get root dir) - let repos = ( - ls ($root | if $recursive { path join "**" "*" ".git" } else { path join "*" "*" "*"}) - | get name - | replace slashes - | str replace --regex $"^($root | replace slashes)" "" - | str replace --regex $".git$" "" - | str trim -l -c '/' - | parse "{host}/{user}/{project}" - | insert user-project {|it| [$it.user $it.project] | path join} - | insert host-user-project {|it| [$it.host $it.user $it.project] | path join} - ) - - let repos = ($repos | if $query != null { - if $exact { - where {|it| ( - ($it.project == $query) or - ($it.user-project == $query) or - ($it.host-user-project == $query) - )} - } else { - find $query - } - } else {}) - - $repos | get host-user-project | if $full_path { - each {|repo| $root | path join $repo} - } else {} -} - -export def "pick repo" [ - prompt: string - query: string -] { - list repos --exact false --full-path false --recursive false - | if $query == null {} else { find $query } - | input list --fuzzy $prompt -} - -#[cfg(test)] -export module tests { - use std assert - use std log - - #[test] - export def parse-project-test [] { - log debug "testing empty input" - assert equal (parse project "") {project: ""} - - # normal parsing - log debug "testing some normal parsing" - let expected = {host: "host", user: "user", project: "project"} - assert equal (parse project "/host/user/project") $expected - assert equal (parse project "host/user/project/") $expected - assert equal (parse project "host//user/project") $expected - assert equal (parse project "host/user/project") $expected - assert equal (parse project "host/user/project.git") $expected - assert equal (parse project "http://host/user/project") $expected - assert equal (parse project "https://host/user/project") $expected - assert equal (parse project "ssh://host/user/project") $expected - assert equal (parse project "git@host:user/project.git") $expected - - # subgroups? - log debug "testing parsing of subgroups" - assert equal (parse project "host/user/group/subgroup/subsubgroup/project") { - host: "host", user: "user", project: "group/subgroup/subsubgroup/project" - } - - # default values... - log debug "testing missing fields" - assert equal (parse project "user/project") {user: "user", project: "project"} - assert equal (parse project "project") {project: "project"} - - # ... with subgroups? - # we cannot parse these properly, that will throw a runtime HTTP error - log debug "testing imperfect subgroups" - assert equal (parse project "user/group/subgroup/subsubgroup/project") { - host: "user", user: "group", project: "subgroup/subsubgroup/project" - } - assert equal (parse project "group/subgroup/subsubgroup/project") { - host: "group", user: "subgroup", project: "subsubgroup/project" - } - - log debug "testing invalid project name" - assert equal (parse project "git#host:user/project.git") { - host: "git#host", user: "user", project: "project" - } - } - - def default-project-test-template [] { - assert equal ($in | default project | columns | sort) ["host" "project" "user"] - } - - #[test] - export def default-project-test [] { - for project in [ - {project: "foo"} - {project: "foo", user: "bar"} - {project: "foo", user: "bar", host: "baz"} - ] { - $project | default-project-test-template - } - } -} diff --git a/nu-git-manager/src/sugar/completions/git.nu b/nu-git-manager/src/sugar/completions/git.nu deleted file mode 100644 index 6865fe07..00000000 --- a/nu-git-manager/src/sugar/completions/git.nu +++ /dev/null @@ -1,457 +0,0 @@ -def is-git-repo [] { - not (do -i { - git rev-parse --is-inside-work-tree - } | is-empty) -} - -def "nu-complete git available upstream" [] { - if not (is-git-repo) { return } - - ^git branch -a | lines | each { |line| $line | str replace --regex '\* ' "" | str trim } -} - -def "nu-complete git remotes" [] { - if not (is-git-repo) { return } - - ^git remote | lines | each { |line| $line | str trim } -} - -def "nu-complete git log" [] { - if not (is-git-repo) { return } - - ^git log --pretty=%h | lines | each { |line| $line | str trim } -} - -# Yield all existing commits in descending chronological order. -def "nu-complete git commits all" [] { - if not (is-git-repo) { return } - - ^git rev-list --all --remotes --pretty=oneline | lines | parse "{value} {description}" -} - -# Yield commits of current branch only. This is useful for e.g. cut points in -# `git rebase`. -def "nu-complete git commits current branch" [] { - if not (is-git-repo) { return } - - ^git log --pretty="%h %s" | lines | parse "{value} {description}" -} - -# Yield local branches like `main`, `feature/typo_fix` -def "nu-complete git local branches" [] { - if not (is-git-repo) { return } - - ^git branch | lines | each { |line| $line | str replace --regex '\* ' "" | str trim } -} - -# Yield remote branches like `origin/main`, `upstream/feature-a` -def "nu-complete git remote branches with prefix" [] { - if not (is-git-repo) { return } - - ^git branch -r | lines | parse -r '^\*?(\s*|\s*\S* -> )(?P\S*$)' | get branch | uniq -} - -# Yield remote branches *without* prefix which do not have a local counterpart. -# E.g. `upstream/feature-a` as `feature-a` to checkout and track in one command -# with `git checkout` or `git switch`. -def "nu-complete git remote branches nonlocal without prefix" [] { - if not (is-git-repo) { return } - - # Get regex to strip remotes prefixes. It will look like `(origin|upstream)` - # for the two remotes `origin` and `upstream`. - let remotes_regex = (["(", ((nu-complete git remotes | each {|r| [$r, '/'] | str join}) | str join "|"), ")"] | str join) - let local_branches = (nu-complete git local branches) - ^git branch -r | lines | parse -r (['^[\* ]+', $remotes_regex, '?(?P\S+)'] | flatten | str join) | get branch | uniq | where {|branch| $branch != "HEAD"} | where {|branch| $branch not-in $local_branches } -} - -def "nu-complete git switch" [] { - if not (is-git-repo) { return } - - (nu-complete git local branches) - | parse "{value}" - | insert description "local branch" - | append (nu-complete git remote branches nonlocal without prefix - | parse "{value}" - | insert description "remote branch") -} - -def "nu-complete git checkout" [] { - if not (is-git-repo) { return } - - (nu-complete git local branches) - | parse "{value}" - | insert description "local branch" - | append (nu-complete git remote branches nonlocal without prefix - | parse "{value}" - | insert description "remote branch") - | append (nu-complete git remote branches with prefix - | parse "{value}" - | insert description "remote branch") - | append (nu-complete git commits all) -} - -# Arguments to `git rebase --onto ` -def "nu-complete git rebase" [] { - if not (is-git-repo) { return } - - (nu-complete git local branches) - | parse "{value}" - | insert description "local branch" - | append (nu-complete git remote branches with prefix - | parse "{value}" - | insert description "remote branch") - | append (nu-complete git commits all) -} - -def "nu-complete git stash-list" [] { - if not (is-git-repo) { return } - - git stash list | lines | parse "{value}: {description}" -} - -def "nu-complete git tags" [] { - if not (is-git-repo) { return } - - ^git tag | lines -} - -def "nu-complete git built-in-refs" [] { - if not (is-git-repo) { return } - - [HEAD FETCH_HEAD ORIG_HEAD] -} - -def "nu-complete git refs" [] { - if not (is-git-repo) { return } - - nu-complete git switchable branches - | parse "{value}" - | insert description Branch - | append (nu-complete git tags | parse "{value}" | insert description Tag) - | append (nu-complete git built-in-refs) -} - -def "nu-complete git subcommands" [] { - if not (is-git-repo) { return } - - ^git help -a | lines | where $it starts-with " " | parse -r '\s*(?P[^ ]+) \s*(?P\w.*)' -} - -# Check out git branches and files -export extern "git checkout" [ - ...targets: string@"nu-complete git checkout" # name of the branch or files to checkout - --conflict: string # conflict style (merge or diff3) - --detach(-d) # detach HEAD at named commit - --force(-f) # force checkout (throw away local modifications) - --guess # second guess 'git checkout ' (default) - --ignore-other-worktrees # do not check if another worktree is holding the given ref - --ignore-skip-worktree-bits # do not limit pathspecs to sparse entries only - --merge(-m) # perform a 3-way merge with the new branch - --orphan: string # new unparented branch - --ours(-2) # checkout our version for unmerged files - --overlay # use overlay mode (default) - --overwrite-ignore # update ignored files (default) - --patch(-p) # select hunks interactively - --pathspec-from-file: string # read pathspec from file - --progress # force progress reporting - --quiet(-q) # suppress progress reporting - --recurse-submodules: string # control recursive updating of submodules - --theirs(-3) # checkout their version for unmerged files - --track(-t) # set upstream info for new branch - -b: string # create and checkout a new branch - -B: string # create/reset and checkout a branch - -l # create reflog for new branch -] - -# Download objects and refs from another repository -export extern "git fetch" [ - repository?: string@"nu-complete git remotes" # name of the branch to fetch - --all # Fetch all remotes - --append(-a) # Append ref names and object names to .git/FETCH_HEAD - --atomic # Use an atomic transaction to update local refs. - --depth: int # Limit fetching to n commits from the tip - --deepen: int # Limit fetching to n commits from the current shallow boundary - --shallow-since: string # Deepen or shorten the history by date - --shallow-exclude: string # Deepen or shorten the history by branch/tag - --unshallow # Fetch all available history - --update-shallow # Update .git/shallow to accept new refs - --negotiation-tip: string # Specify which commit/glob to report while fetching - --negotiate-only # Do not fetch, only print common ancestors - --dry-run # Show what would be done - --write-fetch-head # Write fetched refs in FETCH_HEAD (default) - --no-write-fetch-head # Do not write FETCH_HEAD - --force(-f) # Always update the local branch - --keep(-k) # Keep dowloaded pack - --multiple # Allow several arguments to be specified - --auto-maintenance # Run 'git maintenance run --auto' at the end (default) - --no-auto-maintenance # Don't run 'git maintenance' at the end - --auto-gc # Run 'git maintenance run --auto' at the end (default) - --no-auto-gc # Don't run 'git maintenance' at the end - --write-commit-graph # Write a commit-graph after fetching - --no-write-commit-graph # Don't write a commit-graph after fetching - --prefetch # Place all refs into the refs/prefetch/ namespace - --prune(-p) # Remove obsolete remote-tracking references - --prune-tags(-P) # Remove any local tags that do not exist on the remote - --no-tags(-n) # Disable automatic tag following - --refmap: string # Use this refspec to map the refs to remote-tracking branches - --tags(-t) # Fetch all tags - --recurse-submodules: string # Fetch new commits of populated submodules (yes/on-demand/no) - --jobs(-j): int # Number of parallel children - --no-recurse-submodules # Disable recursive fetching of submodules - --set-upstream # Add upstream (tracking) reference - --submodule-prefix: string # Prepend to paths printed in informative messages - --upload-pack: string # Non-default path for remote command - --quiet(-q) # Silence internally used git commands - --verbose(-v) # Be verbose - --progress # Report progress on stderr - --server-option(-o): string # Pass options for the server to handle - --show-forced-updates # Check if a branch is force-updated - --no-show-forced-updates # Don't check if a branch is force-updated - -4 # Use IPv4 addresses, ignore IPv6 addresses - -6 # Use IPv6 addresses, ignore IPv4 addresses -] - -# Push changes -export extern "git push" [ - remote?: string@"nu-complete git remotes", # the name of the remote - ...refs: string@"nu-complete git local branches" # the branch / refspec - --all # push all refs - --atomic # request atomic transaction on remote side - --delete(-d) # delete refs - --dry-run(-n) # dry run - --exec: string # receive pack program - --follow-tags # push missing but relevant tags - --force-with-lease # require old value of ref to be at this value - --force(-f) # force updates - --ipv4(-4) # use IPv4 addresses only - --ipv6(-6) # use IPv6 addresses only - --mirror # mirror all refs - --no-verify # bypass pre-push hook - --porcelain # machine-readable output - --progress # force progress reporting - --prune # prune locally removed refs - --push-option(-o): string # option to transmit - --quiet(-q) # be more quiet - --receive-pack: string # receive pack program - --recurse-submodules: string # control recursive pushing of submodules - --repo: string # repository - --set-upstream(-u) # set upstream for git pull/status - --signed: string # GPG sign the push - --tags # push tags (can't be used with --all or --mirror) - --thin # use thin pack - --verbose(-v) # be more verbose -] - -# Pull changes -export extern "git pull" [ - remote?: string@"nu-complete git remotes", # the name of the remote - ...refs: string@"nu-complete git local branches" # the branch / refspec - --rebase # rebase current branch on top of upstream after fetching -] - -# Switch between branches and commits -export extern "git switch" [ - switch?: string@"nu-complete git switch" # name of branch to switch to - --create(-c): string # create a new branch - --detach(-d): string@"nu-complete git log" # switch to a commit in a detatched state - --force-create(-C): string # forces creation of new branch, if it exists then the existing branch will be reset to starting point - --force(-f) # alias for --discard-changes - --guess # if there is no local branch which matches then name but there is a remote one then this is checked out - --ignore-other-worktrees # switch even if the ref is held by another worktree - --merge(-m) # attempts to merge changes when switching branches if there are local changes - --no-guess # do not attempt to match remote branch names - --no-progress # do not report progress - --no-recurse-submodules # do not update the contents of sub-modules - --no-track # do not set "upstream" configuration - --orphan: string # create a new orphaned branch - --progress # report progress status - --quiet(-q) # suppress feedback messages - --recurse-submodules # update the contents of sub-modules - --track(-t) # set "upstream" configuration -] - -# Apply the change introduced by an existing commit -export extern "git cherry-pick" [ - ...commit: string@"nu-complete git commits all" # The commit ID to be cherry-picked - --edit(-e) # Edit the commit message prior to committing - --no-commit(-n) # Apply changes without making any commit - --signoff(-s) # Add Signed-off-by line to the commit message - --ff # Fast-forward if possible - --continue # Continue the operation in progress - --abort # Cancel the operation - --skip # Skip the current commit and continue with the rest of the sequence -] - -# Rebase the current branch -export extern "git rebase" [ - branch?: string@"nu-complete git rebase" # name of the branch to rebase onto - upstream?: string@"nu-complete git rebase" # upstream branch to compare against - --continue # restart rebasing process after editing/resolving a conflict - --abort # abort rebase and reset HEAD to original branch - --quit # abort rebase but do not reset HEAD - --interactive(-i) # rebase interactively with list of commits in editor - --onto?: string@"nu-complete git rebase" # starting point at which to create the new commits - --root # start rebase from root commit -] - -# List or change branches -export extern "git branch" [ - branch?: string@"nu-complete git local branches" # name of branch to operate on - --abbrev # use short commit hash prefixes - --edit-description # open editor to edit branch description - --merged # list reachable branches - --no-merged # list unreachable branches - --set-upstream-to: string@"nu-complete git available upstream" # set upstream for branch - --unset-upstream # remote upstream for branch - --all # list both remote and local branches - --copy # copy branch together with config and reflog - --format # specify format for listing branches - --move # rename branch - --points-at # list branches that point at an object - --show-current # print the name of the current branch - --verbose # show commit and upstream for each branch - --color # use color in output - --quiet # suppress messages except errors - --delete(-d) # delete branch - --list # list branches - --contains: string@"nu-complete git commits all" # show only branches that contain the specified commit - --no-contains # show only branches that don't contain specified commit - --track(-t) # when creating a branch, set upstream -] - -# List or change tracked repositories -export extern "git remote" [ - --verbose(-v) # Show URL for remotes -] - -# Add a new tracked repository -export extern "git remote add" [ -] - -# Rename a tracked repository -export extern "git remote rename" [ - remote: string@"nu-complete git remotes" # remote to rename - new_name: string # new name for remote -] - -# Remove a tracked repository -export extern "git remote remove" [ - remote: string@"nu-complete git remotes" # remote to remove -] - -# Get the URL for a tracked repository -export extern "git remote get-url" [ - remote: string@"nu-complete git remotes" # remote to get URL for -] - -# Set the URL for a tracked repository -export extern "git remote set-url" [ - remote: string@"nu-complete git remotes" # remote to set URL for - url: string # new URL for remote -] - -# Show changes between commits, working tree etc -export extern "git diff" [ - rev1?: string@"nu-complete git refs" - rev2?: string@"nu-complete git refs" - --cached # show staged changes - --name-only # only show names of changed files - --name-status # show changed files and kind of change - --no-color # disable color output -] - -# Commit changes -export extern "git commit" [ - --all(-a) # automatically stage all modified and deleted files - --amend # amend the previous commit rather than adding a new one - --message(-m): string # specify the commit message rather than opening an editor - --no-edit # don't edit the commit message (useful with --amend) -] - -# List commits -export extern "git log" [ - # Ideally we'd allow completion of revisions here, but that would make completion of filenames not work. - -U # show diffs - --follow # show history beyond renames (single file only) - --grep: string # show log entries matching supplied regular expression -] - -# Show or change the reflog -export extern "git reflog" [ -] - -# Stage files -export extern "git add" [ - --patch(-p) # interactively choose hunks to stage -] - -# Delete file from the working tree and the index -export extern "git rm" [ - -r # recursive -] - -# Show the working tree status -export extern "git status" [ - --verbose(-v) # verbose -] - -# Stash changes for later -export extern "git stash push" [ - --patch(-p) # interactively choose hunks to stash -] - -# Unstash previously stashed changes -export extern "git stash pop" [ -] - -# List stashed changes -export extern "git stash list" [ -] - -# Show a stashed change -export extern "git stash show" [ - stash: string@"nu-complete git stash-list" - -U # show diff -] - -# Drop a stashed change -export extern "git stash drop" [ - stash: string@"nu-complete git stash-list" -] - -# Create a new git repository -export extern "git init" [ - --initial-branch(-b) # initial branch name -] - -# List or manipulate tags -export extern "git tag" [ - --delete(-d): string@"nu-complete git tags" # delete a tag -] - -# Start a binary search to find the commit that introduced a bug -export extern "git bisect start" [ - bad?: string # a commit that has the bug - good?: string # a commit that doesn't have the bug -] - -# Mark the current (or specified) revision as bad -export extern "git bisect bad" [ -] - -# Mark the current (or specified) revision as good -export extern "git bisect good" [ -] - -# Skip the current (or specified) revision -export extern "git bisect skip" [ -] - -# End bisection -export extern "git bisect reset" [ -] - -# Show help for a git subcommand -export extern "git help" [ - command: string@"nu-complete git subcommands" # subcommand to show help for -] diff --git a/nu-git-manager/src/sugar/dotfiles.nu b/nu-git-manager/src/sugar/dotfiles.nu deleted file mode 100644 index e9c3e5be..00000000 --- a/nu-git-manager/src/sugar/dotfiles.nu +++ /dev/null @@ -1,28 +0,0 @@ -# choose a config file to edit with fuzzy finding -# -# `dotfiles edit` requires the following environment variables to be defined: -# - `$env.DOTFILES_GIT_DIR`: the path to the *bare* repository -# - `$env.DOTFILES_WORKTREE`: the path to the worktree, e.g. `$env.HOME` -# - `$env.EDITOR`: will default to `vim` -# -# this command will `cd` into the directory where the chosen file is to allow -# easier editing and will use the `EDITOR`. -export def-env edit [] { - let choice = ( - git --git-dir $env.DOTFILES_GIT_DIR --work-tree $env.DOTFILES_WORKTREE - lf ~ --full-name - | lines - | input list --fuzzy - $"Please (ansi yellow_italic)choose a config(ansi reset) file to (ansi blue_underline)edit(ansi reset): " - | into string - ) - if ($choice | is-empty) { - return - } - - let path = ($env.HOME | path join $choice) - - cd ($path | path dirname) - ^($env.EDITOR | default "vim") ($path | path basename) - cd - -} diff --git a/nu-git-manager/src/sugar/mod.nu b/nu-git-manager/src/sugar/mod.nu deleted file mode 100644 index 38662f45..00000000 --- a/nu-git-manager/src/sugar/mod.nu +++ /dev/null @@ -1 +0,0 @@ -export use completions diff --git a/nu-git-manager/src/sugar/gh.nu b/nu-git-manager/sugar/gh.nu similarity index 98% rename from nu-git-manager/src/sugar/gh.nu rename to nu-git-manager/sugar/gh.nu index 82726a42..0c800fb8 100644 --- a/nu-git-manager/src/sugar/gh.nu +++ b/nu-git-manager/sugar/gh.nu @@ -1,3 +1,4 @@ +use ../fs/dir.nu [open-item] def check-gh-logged-in [] { let out = (do -i { gh auth status } | complete) if $out.exit_code != 0 { @@ -75,7 +76,7 @@ export def "pr open" [ } } | url join) - xdg-open $url + open-item $url } def unpack-pages [] { @@ -163,7 +164,7 @@ export def "me pr" [ if not ($number | is-empty) { if $open_in_browser { - xdg-open ({ + open-item ({ scheme: "https" host: "github.com" path: ($repo | path join "pull" ($number | into string)) @@ -218,7 +219,7 @@ export def "me pr" [ } if $open_in_browser { - xdg-open $choice.url + open-item $choice.url return } diff --git a/nu-git-manager/src/sugar/gist.nu b/nu-git-manager/sugar/gist.nu similarity index 100% rename from nu-git-manager/src/sugar/gist.nu rename to nu-git-manager/sugar/gist.nu diff --git a/nu-git-manager/src/sugar/git.nu b/nu-git-manager/sugar/git.nu similarity index 100% rename from nu-git-manager/src/sugar/git.nu rename to nu-git-manager/sugar/git.nu diff --git a/nu-git-manager/src/sugar/completions/mod.nu b/nu-git-manager/sugar/mod.nu similarity index 100% rename from nu-git-manager/src/sugar/completions/mod.nu rename to nu-git-manager/sugar/mod.nu diff --git a/package.nuon b/package.nuon index 4655c34c..2fe86d0f 100644 --- a/package.nuon +++ b/package.nuon @@ -1,6 +1,6 @@ { name: "nu-git-manager" - version: 0.1.0 + version: 0.2.0 description: "A collection of Nushell tools to manage `git` repositories." documentation: "https://github.com/amtoine/nu-git-manager/blob/main/README.md" maintainers: [ diff --git a/tests/mod.nu b/tests/mod.nu index e69de29b..251ca8fd 100644 --- a/tests/mod.nu +++ b/tests/mod.nu @@ -0,0 +1,90 @@ +use std assert + +use ../nu-git-manager/git/url.nu [parse-git-url, get-fetch-push-urls] +use ../nu-git-manager/fs/store.nu get-repo-store-path +use ../nu-git-manager/fs/path.nu "path sanitize" + +export def path-sanitization [] { + assert equal ('\foo\bar' | path sanitize) "/foo/bar" +} + +export def git-url-parsing [] { + let cases = [ + [input, host, owner, group, repo]; + + ["https://github.com/foo/bar", "github.com", "foo", null, "bar"], + ["https://github.com/foo/bar.git", "github.com", "foo", null, "bar"], + ["https://github.com/foo/bar/tree/branch/file", "github.com", "foo", null, "bar"], + ["ssh://github.com/foo/bar", "github.com", "foo", null, "bar"], + ["git@github.com:foo/bar", "github.com", "foo", null, "bar"], + ["https://gitlab.com/foo/bar", "gitlab.com", "foo", null, "bar"], + ["git@gitlab.com:foo/bar", "gitlab.com", "foo", null, "bar"], + ["git@gitlab.com:foo/bar/baz/brr", "gitlab.com", "foo", "bar/baz", "brr"], + ] + + for case in $cases { + let expected = { + host: $case.host, owner: $case.owner, group: $case.group, repo: $case.repo + } + assert equal ($case.input | parse-git-url) $expected + } +} + +export def fetch-and-push-urls [] { + let cases = [ + [use_ssh, user_fetch, user_push, fetch_protocol, push_protocol]; + + # - if user_fetch is not-empty: fetch_protocol is the same (same for push) + # - if user_fetch is empty: fetch_protocol is `https` if not `use_ssh` (same for push) + [false, "", "", "https", "https"], + [false, "", "ssh", "https", "ssh"], + [false, "", "https", "https", "https"], + [false, "ssh", "", "ssh", "https"], + [false, "ssh", "ssh", "ssh", "ssh"], + [false, "ssh", "https", "ssh", "https"], + [false, "https", "", "https", "https"], + [false, "https", "ssh", "https", "ssh"], + [false, "https", "https", "https", "https"], + [true, "", "", "ssh", "ssh"], + [true, "", "ssh", "ssh", "ssh"], + [true, "", "https", "ssh", "https"], + [true, "ssh", "", "ssh", "ssh"], + [true, "ssh", "ssh", "ssh", "ssh"], + [true, "ssh", "https", "ssh", "https"], + [true, "https", "", "https", "ssh"], + [true, "https", "ssh", "https", "ssh"], + [true, "https", "https", "https", "https"], + ] + + let repo = {host: "h", owner: "o", group: "", repo: "r"} + let base_url = { + scheme: null, + host: $repo.host, + path: ([$repo.owner, $repo.group, $repo.repo] | compact | path join | path sanitize) + } + + for case in $cases { + let actual = get-fetch-push-urls $repo $case.user_fetch $case.user_push $case.use_ssh + let expected = { + fetch: ($base_url | update scheme $case.fetch_protocol | url join) + push: ($base_url | update scheme $case.push_protocol | url join) + } + assert equal $actual $expected $"input: ($case)" + } +} + +export def get-store-root [] { + let cases = [ + [env, expected]; + + [{GIT_REPOS_HOME: null, XDG_DATA_HOME: null}, "~/.local/share/repos"], + [{GIT_REPOS_HOME: "~/my_repos", XDG_DATA_HOME: null}, "~/my_repos"], + [{GIT_REPOS_HOME: null, XDG_DATA_HOME: "~/xdg"}, "~/xdg/repos"], + [{GIT_REPOS_HOME: "~/my_repos", XDG_DATA_HOME: "~/xdg"}, "~/my_repos"], + ] + + for case in $cases { + let actual = with-env $case.env { get-repo-store-path } + assert equal $actual ($case.expected | path expand | path sanitize) + } +} diff --git a/toolkit/mod.nu b/toolkit/mod.nu deleted file mode 100644 index 0075407d..00000000 --- a/toolkit/mod.nu +++ /dev/null @@ -1,43 +0,0 @@ -def pretty-cmd [] { - let cmd = $in - $"(ansi -e {fg: default attr: di})($cmd)(ansi reset)" - -} - -def "nu-complete import-targets" [] { - ["neovim"] -} - -export def "import" [--into: string@"nu-complete import-targets"] { - if ($into == null) { - print $"(ansi red_italic)--into is a required argument.(ansi reset)" - return - } - - # TODO: replace projects with a config value. - let projects = (match $into { - "neovim" => { $env.HOME | path join ".local" "share" "nvim" "project_nvim" "project_history" }, - _ => { - print $"(ansi red) '(ansi red_italic)($into)' is not a valid target.(ansi reset)" - return - }, - }) - - mkdir ($projects | path dirname) - touch $projects - - let before = ($projects | open | lines | length) - - $projects | open | lines | append ( - ghq list # FIXME: do not use `ghq` as the main dependency - | lines - | each {|it| - print $"adding (ansi yellow)($it)(ansi reset) to the projects..." - ghq root | str trim | path join $it # FIXME: do not use `ghq` as the main dependency - } - ) | uniq - | save -f $projects - - print $"all ('git' | pretty-cmd) projects (ansi green_bold)successfully imported(ansi reset) into the ($projects | pretty-cmd) list!" - print $"from ($before) to ($projects | open | lines | length) projects." -}