├── .github └── workflows │ └── ci.yml ├── LICENSE ├── README.md ├── format.nu ├── languages.ncl ├── languages └── nu.scm ├── run_test.nu ├── test ├── expected_attribute.nu ├── expected_command.nu ├── expected_comment.nu ├── expected_ctrl.nu ├── expected_data.nu ├── expected_decl.nu ├── expected_exe.nu ├── expected_keyword.nu ├── expected_string.nu ├── input_attribute.nu ├── input_command.nu ├── input_comment.nu ├── input_ctrl.nu ├── input_data.nu ├── input_decl.nu ├── input_exe.nu ├── input_keyword.nu └── input_string.nu └── toolkit.nu /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: continuous-integration 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | name: Nushell formatter test 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: hustcer/setup-nu@main 16 | with: 17 | version: "*" 18 | - run: nu --version 19 | - name: Setup Rust toolchain and cache 20 | uses: actions-rust-lang/setup-rust-toolchain@v1.10.1 21 | - name: Clone topiary 22 | uses: GuillaumeFalourd/clone-github-repo-action@v2.3 23 | with: 24 | branch: 'main' 25 | owner: 'tweag' 26 | repository: 'topiary' 27 | - name: Install topiary-cli 28 | run: | 29 | cd topiary 30 | topiary --version || cargo install --path topiary-cli 31 | - name: Run tests 32 | run: nu run_test.nu 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 - 2030 BlindFS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Format nushell with Topiary 2 | 3 | [![Build Status](https://img.shields.io/github/actions/workflow/status/blindfs/topiary-nushell/ci.yml?branch=main)](https://github.com/blindfs/topiary-nushell/actions) 4 | 5 | * [Topiary](https://github.com/tweag/topiary): tree-sitter based uniform formatter 6 | * This repo contains: 7 | * languages.ncl: configuration that enables nushell 8 | * nu.scm: tree-sitter query DSL that defines the behavior of the formatter for nushell 9 | * stand-alone tests written in nushell 10 | 11 | ## Status 12 | 13 | * Supposed to work well with all language features of latest nushell (0.103) 14 | 15 | > [!NOTE] 16 | > 17 | > * There're corner cases where `tree-sitter-nu` would fail with parsing errors, if you encounter them, please open an issue [there](https://github.com/nushell/tree-sitter-nu). 18 | > * If you encounter any style/format issue, please report in this repo, any feedback is appreciated. 19 | 20 | ## Setup 21 | 22 | 1. Install topiary-cli using whatever package-manager on your system (0.6.0+ suggested) 23 | 24 | ```nushell 25 | # e.g. installing with cargo 26 | cargo install --git https://github.com/tweag/topiary topiary-cli 27 | ``` 28 | 29 | 2. Clone this repo somewhere 30 | 31 | ```nushell 32 | # e.g. to `$env.XDG_CONFIG_HOME/topiary` 33 | git clone https://github.com/blindFS/topiary-nushell ($env.XDG_CONFIG_HOME | path join topiary) 34 | ``` 35 | 36 | 3. Setup environment variables (Optional) 37 | 38 | > [!WARNING] 39 | > This is required if you want to do the formatting via vanilla topiary-cli, like in the neovim/helix settings below. 40 | > 41 | > While the [`format.nu`](https://github.com/blindFS/topiary-nushell/blob/main/format.nu) script in this repo just wraps that for you. 42 | 43 | ```nushell 44 | # Set environment variables according to the path of the clone 45 | $env.TOPIARY_CONFIG_FILE = ($env.XDG_CONFIG_HOME | path join topiary languages.ncl) 46 | $env.TOPIARY_LANGUAGE_DIR = ($env.XDG_CONFIG_HOME | path join topiary languages) 47 | ``` 48 | 49 | > [!WARNING] 50 | > For windows users, if something went wrong the first time you run the formatter, 51 | > like compiling errors, you might need the following extra steps to make it work. 52 | 53 |
54 | Optional for Windows 55 | 56 | 1. Install the [tree-sitter-cli](https://github.com/tree-sitter/tree-sitter/blob/master/cli/README.md). 57 | 2. Clone [tree-sitter-nu](https://github.com/nushell/tree-sitter-nu) somewhere and cd into it. 58 | 3. Build the parser manually with `tree-sitter build`. 59 | 4. Replace the `languages.ncl` file in this repo with something like: 60 | 61 | ```ncl 62 | { 63 | languages = { 64 | nu = { 65 | extensions = ["nu"], 66 | grammar.source.path = "C:/path/to/tree-sitter-nu/nu.dll", 67 | symbol = "tree_sitter_nu", 68 | }, 69 | }, 70 | } 71 | ``` 72 | 73 |
74 | 75 | ## Usage 76 | 77 |
78 | Using the format.nu wrapper 79 | 80 | ```markdown 81 | Helper to run topiary with the correct environment variables for topiary-nushell 82 | 83 | Usage: 84 | > format.nu {flags} ...(files) 85 | 86 | Flags: 87 | -c, --config_dir : Root of the topiary-nushell repo, defaults to the parent directory of this script 88 | -h, --help: Display the help message for this command 89 | 90 | Parameters: 91 | ...files : Files to format 92 | 93 | Input/output types: 94 | ╭───┬─────────┬─────────╮ 95 | │ # │ input │ output │ 96 | ├───┼─────────┼─────────┤ 97 | │ 0 │ nothing │ nothing │ 98 | │ 1 │ string │ string │ 99 | ╰───┴─────────┴─────────╯ 100 | 101 | Examples: 102 | Read from stdin 103 | > bat foo.nu | format.nu 104 | 105 | Format files (in-place replacement) 106 | > format.nu foo.nu bar.nu 107 | 108 | Path overriding 109 | > format.nu -c /path/to/topiary-nushell foo.nu bar.nu 110 | ``` 111 | 112 |
113 | 114 |
115 | Using topiary-cli 116 | 117 | ```nushell 118 | # in-place formatting 119 | topiary format script.nu 120 | # stdin -> stdout 121 | cat foo.nu | topiary format --language nu 122 | ``` 123 | 124 |
125 | 126 | ## Editor Integration 127 | 128 |
129 | Neovim 130 | Format on save with conform.nvim: 131 | 132 | ```lua 133 | -- lazy.nvim setup 134 | { 135 | "stevearc/conform.nvim", 136 | dependencies = { "mason.nvim" }, 137 | event = "VeryLazy", 138 | opts = { 139 | formatters_by_ft = { 140 | nu = { "topiary_nu" }, 141 | }, 142 | formatters = { 143 | topiary_nu = { 144 | command = "topiary", 145 | args = { "format", "--language", "nu" }, 146 | }, 147 | }, 148 | }, 149 | }, 150 | ``` 151 | 152 |
153 | 154 |
155 | Helix 156 | 157 | To format on save in Helix, add this configuration to your `helix/languages.toml`. 158 | 159 | ```toml 160 | [[language]] 161 | name = "nu" 162 | auto-format = true 163 | formatter = { command = "topiary", args = ["format", "--language", "nu"] } 164 | ``` 165 | 166 |
167 | 168 |
169 | Zed 170 | 171 | ```json 172 | "languages": { 173 | "Nu": { 174 | "formatter": { 175 | "external": { 176 | "command": "/path-to-the-clone/format.nu" 177 | } 178 | }, 179 | "format_on_save": "on" 180 | } 181 | } 182 | ``` 183 | 184 |
185 | 186 | ## Contribute 187 | 188 | > [!IMPORTANT] 189 | > Help to find format issues with following method 190 | > (dry-run, detects parsing/idempotence/semantic breaking): 191 | 192 | ```nushell 193 | source toolkit.nu 194 | test_format 195 | ``` 196 | -------------------------------------------------------------------------------- /format.nu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nu --stdin 2 | 3 | const script_path = path self . 4 | 5 | # Helper to run topiary with the correct environment variables for topiary-nushell 6 | @example "Read from stdin" { bat foo.nu | format.nu } 7 | @example "Format files (in-place replacement)" { format.nu foo.nu bar.nu } 8 | @example "Path overriding" { format.nu -c /path/to/topiary-nushell foo.nu bar.nu } 9 | def main [ 10 | --config_dir (-c): path # Root of the topiary-nushell repo, defaults to the parent directory of this script 11 | ...files: path # Files to format 12 | ]: [nothing -> nothing string -> string] { 13 | let config_dir = $config_dir | default $script_path 14 | $env.TOPIARY_CONFIG_FILE = ($config_dir | path join languages.ncl) 15 | $env.TOPIARY_LANGUAGE_DIR = ($config_dir | path join languages) 16 | 17 | if ($files | is-not-empty) { 18 | topiary format ...$files 19 | } else { 20 | topiary format --language nu 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /languages.ncl: -------------------------------------------------------------------------------- 1 | { 2 | languages = { 3 | nu = { 4 | extensions = ["nu"], 5 | grammar.source.git = { 6 | git = "https://github.com/nushell/tree-sitter-nu.git", 7 | rev = "c10340b5bb3789f69182acf8f34c3d4fc24d2fe1", 8 | }, 9 | }, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /languages/nu.scm: -------------------------------------------------------------------------------- 1 | ;; leaf nodes are left intact 2 | [ 3 | (cell_path) 4 | (comment) 5 | (shebang) 6 | (unquoted) 7 | (val_binary) 8 | (val_bool) 9 | (val_date) 10 | (val_duration) 11 | (val_filesize) 12 | (val_nothing) 13 | (val_number) 14 | (val_string) 15 | (val_variable) 16 | ] @leaf 17 | 18 | ;; keep empty lines 19 | (_) @allow_blank_line_before 20 | 21 | [ 22 | ":" 23 | ";" 24 | "do" 25 | "if" 26 | "match" 27 | "try" 28 | "while" 29 | (env_var) 30 | ] @append_space 31 | 32 | [ 33 | "=" 34 | (match_guard) 35 | (short_flag) 36 | (long_flag) 37 | ] @prepend_space 38 | 39 | ;; FIXME: temp workaround for the whitespace issue 40 | (short_flag "=" @prepend_antispace) 41 | (long_flag "=" @prepend_antispace) 42 | (param_value "=" @append_space) 43 | 44 | (assignment 45 | opr: _ 46 | rhs: (pipeline 47 | (pipe_element 48 | (val_string 49 | (raw_string_begin) 50 | ) 51 | ) 52 | ) @prepend_space 53 | ) 54 | 55 | ( 56 | "=" 57 | . 58 | (pipeline 59 | (pipe_element 60 | (val_string 61 | (raw_string_begin) 62 | ) 63 | ) 64 | ) @prepend_space 65 | ) 66 | 67 | [ 68 | "->" 69 | "=>" 70 | "alias" 71 | "as" 72 | "catch" 73 | "const" 74 | "def" 75 | "else" 76 | "error" 77 | "export" 78 | "export-env" 79 | "extern" 80 | "for" 81 | "hide" 82 | "hide-env" 83 | "in" 84 | "let" 85 | "loop" 86 | "make" 87 | "module" 88 | "mut" 89 | "new" 90 | "overlay" 91 | "return" 92 | "source" 93 | "source-env" 94 | "use" 95 | "where" 96 | ] @prepend_space @append_space 97 | 98 | (pipeline 99 | "|" @append_space @prepend_input_softline 100 | ) 101 | 102 | ;; add spaces to left & right sides of operators 103 | (expr_binary 104 | opr: _ @append_input_softline @prepend_input_softline 105 | ) 106 | 107 | (assignment opr: _ @prepend_space) 108 | 109 | (where_command 110 | opr: _ @append_input_softline @prepend_input_softline 111 | ) 112 | 113 | ;; special flags 114 | ( 115 | [ 116 | (short_flag) 117 | (long_flag) 118 | ] @append_space 119 | . 120 | (_) 121 | ) 122 | 123 | (overlay_hide 124 | overlay: _ @prepend_space 125 | ) 126 | 127 | ;; FIXME: temp workaround for the whitespace issue 128 | (hide_env 129 | [ 130 | (short_flag) 131 | (long_flag) 132 | ] @append_antispace 133 | . 134 | (_) 135 | ) 136 | 137 | (hide_env 138 | (identifier) @append_space 139 | . 140 | (identifier) 141 | ) 142 | 143 | (hide_mod 144 | (_) @append_space 145 | . 146 | (_) 147 | ) 148 | 149 | ;; indentation 150 | [ 151 | "[" 152 | "(" 153 | "...(" 154 | "...[" 155 | "...{" 156 | ] @append_indent_start @append_empty_softline 157 | 158 | [ 159 | "]" 160 | "}" 161 | ")" 162 | ] @prepend_indent_end @prepend_empty_softline 163 | 164 | ;;; change line happens after || for closure 165 | "{" @append_indent_start 166 | ( 167 | "{" @append_empty_softline 168 | . 169 | (parameter_pipes)? @do_nothing 170 | ) 171 | 172 | ;; space/newline between parameters 173 | (parameter_pipes 174 | ( 175 | (parameter) @append_space 176 | . 177 | (parameter) 178 | )? 179 | ) @append_space @append_spaced_softline 180 | 181 | (parameter_bracks 182 | (parameter) @append_space 183 | . 184 | (parameter) @prepend_empty_softline 185 | ) 186 | 187 | (parameter 188 | param_long_flag: _? @prepend_space 189 | . 190 | flag_capsule: _? @prepend_space 191 | ) 192 | 193 | ;; attributes 194 | (attribute 195 | (attribute_identifier) 196 | (_)? @prepend_space 197 | ) @append_hardline 198 | 199 | (attribute_list 200 | ";" @delete @append_hardline 201 | ) 202 | 203 | ;; declarations 204 | (decl_def 205 | (long_flag)? @prepend_space @append_space 206 | quoted_name: _? @prepend_space @append_space 207 | unquoted_name: _? @prepend_space @append_space 208 | (returns)? 209 | (block) @prepend_space 210 | ) 211 | 212 | (returns 213 | ":"? @do_nothing 214 | ) @prepend_space 215 | 216 | (returns 217 | type: _ @append_space 218 | . 219 | type: _ 220 | ) 221 | 222 | (decl_use (_) @prepend_space) 223 | (decl_extern (_) @prepend_space) 224 | (decl_module (_) @prepend_space) 225 | 226 | ;; newline 227 | (comment) @prepend_input_softline @append_hardline 228 | 229 | ;; TODO: pseudo scope_id to cope with 230 | ;; https://github.com/tree-sitter/tree-sitter/discussions/3967 231 | (nu_script 232 | (_) @append_begin_scope 233 | . 234 | (_) @prepend_end_scope @prepend_input_softline 235 | (#scope_id! "consecutive_scope") 236 | ) 237 | 238 | (block 239 | (_) @append_begin_scope 240 | . 241 | (_) @prepend_end_scope @prepend_input_softline 242 | (#scope_id! "consecutive_scope") 243 | ) 244 | 245 | (block 246 | "{" @append_space 247 | "}" @prepend_space 248 | ) 249 | 250 | (val_closure 251 | (_) @append_begin_scope 252 | . 253 | (_) @prepend_end_scope @prepend_input_softline 254 | (#scope_id! "consecutive_scope") 255 | ) 256 | 257 | (val_closure 258 | "{" @append_space 259 | . 260 | (parameter_pipes)? @do_nothing 261 | ) 262 | 263 | (val_closure "}" @prepend_space) 264 | 265 | ;; control flow 266 | (ctrl_if 267 | "if" @append_space 268 | condition: _ @append_space 269 | then_branch: _ 270 | "else"? @prepend_input_softline 271 | ) 272 | 273 | (ctrl_for 274 | "for" @append_space 275 | "in" @prepend_space @append_space 276 | body: _ @prepend_space 277 | ) 278 | 279 | (ctrl_while 280 | "while" @append_space 281 | condition: _ @append_space 282 | ) 283 | 284 | (ctrl_match 285 | "match" @append_space 286 | scrutinee: _? @append_space 287 | (match_arm)? @prepend_spaced_softline 288 | (default_arm)? @prepend_spaced_softline 289 | ) 290 | 291 | (ctrl_do (_) @prepend_input_softline) 292 | 293 | ;; data structures 294 | (command_list 295 | [ 296 | (cmd_identifier) 297 | (val_string) 298 | ] @append_space @prepend_spaced_softline 299 | ) 300 | 301 | (command 302 | flag: _? @prepend_input_softline 303 | arg_str: _? @prepend_input_softline 304 | arg_spread: _? @prepend_input_softline 305 | arg: _? @prepend_input_softline 306 | ) 307 | 308 | (redirection 309 | file_path: _? @prepend_input_softline 310 | ) @prepend_input_softline 311 | 312 | (list_body 313 | entry: _ @append_space 314 | . 315 | entry: _ @prepend_spaced_softline 316 | ) 317 | 318 | ;; match_arm 319 | (val_list 320 | entry: _ @append_space 321 | . 322 | entry: _ @prepend_spaced_softline 323 | ) 324 | 325 | (val_table 326 | row: _ @prepend_spaced_softline 327 | ) 328 | 329 | (val_record 330 | entry: (record_entry) @append_space 331 | . 332 | entry: (record_entry) @prepend_spaced_softline 333 | ) 334 | 335 | (record_body 336 | entry: (record_entry) @append_space 337 | . 338 | entry: (record_entry) @prepend_spaced_softline 339 | ) 340 | 341 | (collection_type 342 | type: _ @append_delimiter 343 | . 344 | key: _ @prepend_space 345 | (#delimiter! ",") 346 | ) 347 | -------------------------------------------------------------------------------- /run_test.nu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nu 2 | 3 | use std assert 4 | 5 | const temp_file = 'test/temp.nu' 6 | let files = glob test/input_*.nu 7 | 8 | $env.TOPIARY_CONFIG_FILE = (pwd | path join languages.ncl) 9 | $env.TOPIARY_LANGUAGE_DIR = (pwd | path join languages) 10 | 11 | for f in $files { 12 | print $"(ansi green)Testing: (ansi yellow)($f)(ansi reset)" 13 | cp $f $temp_file 14 | topiary format $temp_file 15 | let expected_file = $f | str replace --regex '/input_' '/expected_' 16 | try { 17 | assert ((open $temp_file) == (open $expected_file)) 18 | } catch { 19 | delta $temp_file $expected_file 20 | } 21 | rm $temp_file 22 | } 23 | -------------------------------------------------------------------------------- /test/expected_attribute.nu: -------------------------------------------------------------------------------- 1 | @test 2 | def test [] { } 3 | @test 4 | def test [] { } 5 | 6 | @example "adding some dummy paths to an empty PATH" { 7 | with-env {PATH: []} { 8 | path add "returned" --ret 9 | } 10 | } --result [returned] 11 | @example "adding paths based on the operating system" { 12 | path add {linux: "foo"} 13 | } 14 | export def --env "path add" [ 15 | --ret (-r) # return $env.PATH, useful in pipelines to avoid scoping. 16 | --append (-a) # append to $env.PATH instead of prepending to. 17 | p 18 | ] { } 19 | 20 | @search-terms multiply times 21 | @example "random" { 2 | double } 22 | @test 23 | def double []: [number -> number] { $in * 2 } 24 | -------------------------------------------------------------------------------- /test/expected_command.nu: -------------------------------------------------------------------------------- 1 | # local command 2 | ls | get -i name 3 | | length; ls # multiline command 4 | | length 5 | # external command 6 | ^git add ( 7 | ls 8 | | get -i name 9 | ) 10 | FOO=BAR BAR=BAZ ^$cmd --arg1=val1 -arg2 arg=value arg=($arg3) 11 | cat unknown.txt o+e> (null-device) 12 | 13 | $hash | $in + "\n" out>> $NUENV_FILE 14 | -------------------------------------------------------------------------------- /test/expected_comment.nu: -------------------------------------------------------------------------------- 1 | # comment at the top 2 | 3 | # comment before command 4 | # multiline 5 | def foo_bar [ 6 | # comment at [ 7 | foo: string # comment for arg 8 | bar: int # another comment 9 | ] { 10 | # comment at { 11 | # comment in body 12 | [ 13 | foo # comment in list 14 | # another comment 15 | bar 16 | ]; 17 | { 18 | foo: foo # comment in record 19 | # another comment 20 | bar: bar 21 | } # comment at } 22 | ( 23 | # comment at ( 24 | foo # comment in parenthesis 25 | bar # another comment 26 | ) 27 | } 28 | 29 | # top level comment 30 | # multiline 31 | -------------------------------------------------------------------------------- /test/expected_ctrl.nu: -------------------------------------------------------------------------------- 1 | # for 2 | for i in [1 2 3] { 3 | # if 4 | if (true or false) { 5 | print "break"; break # break 6 | } else if not false { 7 | print "continue"; continue # continue 8 | } 9 | return 1 # return 10 | } 11 | # alias 12 | alias ll = ls -l # alias comment 13 | # where 14 | ls | where $in.name == 'foo' 15 | | where {|e| $e.item.name !~ $'^($e.index + 1)' } 16 | # match 17 | let foo = {name: 'bar' count: 7} 18 | match $foo { 19 | {name: 'bar' count: $it} if $it < 5 => ($it + 3) # match arm comment 20 | # match comment 21 | {name: 'bar' count: $it} if not ($it >= 5) => ($it + 7) 22 | _ => { exit 0 } 23 | } 24 | # while 25 | mut x = 0; while $x < 10 { $x = $x + 1 }; $x # while comment 26 | # loop 27 | loop { 28 | if $x > 10 { break }; 29 | $x = $x + 1 30 | } 31 | # try 32 | try { 33 | # error 34 | error make -u { 35 | msg: 'Some error info' 36 | } 37 | }; print 'Resuming' 38 | -------------------------------------------------------------------------------- /test/expected_data.nu: -------------------------------------------------------------------------------- 1 | export const config = { 2 | default: { 3 | align: center 4 | updates: when_shown 5 | padding_left: 2 6 | padding_right: 2 7 | icon.font: "Sarasa Term SC Nerd:Bold:17.0" 8 | label.font: "Sarasa Term SC Nerd:Bold:12.0" 9 | icon.color: $colors.white 10 | label.color: $colors.fg 11 | icon.padding_left: 8 12 | icon.padding_right: 2 13 | label.padding_left: 2 14 | label.padding_right: 8 15 | background.corner_radius: 10 16 | background.color: $colors.bg 17 | background.border_width: 1 18 | background.border_color: $colors.bg 19 | } 20 | 21 | workspace_default_args: { 22 | icon.font.size: 12 23 | label.font.size: 17 24 | label.shadow.drawing: on 25 | label.shadow.color: $colors.bg 26 | label.shadow.distance: 3 27 | label.highlight_color: $colors.green 28 | background.drawing: off 29 | background.color: $colors.transparent 30 | background.border_width: 2 31 | background.border_color: $colors.fg 32 | background.shadow.drawing: on 33 | background.shadow.color: $colors.bg 34 | background.shadow.distance: 3 35 | } 36 | 37 | plugin_configs: [ 38 | { 39 | name: ws_listener 40 | pos: left 41 | events: [aerospace_workspace_change space_windows_change] 42 | args: { 43 | updates: on 44 | drawing: off 45 | script: '{}/aerospace.nu' 46 | } 47 | } 48 | { 49 | name: front_app 50 | pos: left 51 | events: [front_app_switched aerospace_mode_change] 52 | args: { 53 | label.color: $colors.black 54 | icon.color: $colors.black 55 | icon.font.size: 20 56 | background.color: $colors.blue 57 | background.corner_radius: 3 58 | background.shadow.drawing: on 59 | background.shadow.color: $colors.bg 60 | background.shadow.distance: 3 61 | script: '{}/front_app.nu' 62 | } 63 | } 64 | { 65 | name: media 66 | pos: center 67 | events: [media_change] 68 | args: { 69 | icon: '󰐎' 70 | icon.color: $colors.fg 71 | label.color: $colors.fg 72 | background.color: $colors.bg 73 | background.border_color: $colors.fg 74 | script: '{}/media.nu' 75 | } 76 | } 77 | { 78 | name: media_cover 79 | pos: center 80 | events: [media_change] 81 | args: { 82 | icon.drawing: off 83 | label.drawing: off 84 | background.image.drawing: on 85 | background.image: media.artwork 86 | background.image.scale: 0.7 87 | background.color: $colors.transparent 88 | background.border_width: 0 89 | } 90 | } 91 | { 92 | name: clock 93 | args: { 94 | update_freq: 30 95 | icon: '' 96 | script: '{}/clock.nu' 97 | background.corner_radius: 3 98 | padding_right: 1 99 | padding_left: 1 100 | label.font.size: 10 101 | } 102 | } 103 | { 104 | name: volume 105 | events: [volume_change] 106 | args: { 107 | script: '{}/volume.nu' 108 | background.corner_radius: 3 109 | padding_right: 1 110 | padding_left: 1 111 | } 112 | } 113 | { 114 | name: battery 115 | events: [system_woke power_source_change] 116 | args: { 117 | update_freq: 120 118 | script: '{}/battery.nu' 119 | click_script: 'open x-apple.systempreferences:com.apple.preference.battery' 120 | background.corner_radius: 3 121 | padding_right: 1 122 | } 123 | } 124 | { 125 | name: separator_right 126 | args: { 127 | icon: '' 128 | padding_left: 0 129 | label.drawing: off 130 | background.drawing: off 131 | click_script: '{}/toggle_stats.nu' 132 | } 133 | } 134 | { 135 | name: disk 136 | args: { 137 | icon: '' 138 | update_freq: 120 139 | script: '{}/disk.nu' 140 | click_script: 'open -a "Disk Utility"' 141 | icon.color: $colors.green 142 | background.border_color: $colors.green 143 | } 144 | } 145 | { 146 | name: cpu 147 | args: { 148 | icon: '' 149 | update_freq: 10 150 | script: '{}/cpu.nu' 151 | click_script: 'open -a "Activity Monitor"' 152 | icon.color: $colors.yellow 153 | background.border_color: $colors.yellow 154 | } 155 | } 156 | { 157 | name: memory 158 | args: { 159 | icon: '﬙' 160 | update_freq: 10 161 | script: '{}/mem.nu' 162 | click_script: 'open -a "Activity Monitor"' 163 | icon.color: $colors.cyan 164 | background.border_color: $colors.cyan 165 | } 166 | } 167 | { 168 | name: temp_cpu 169 | args: { 170 | icon: '' 171 | label.font.size: 7 172 | label.y_offset: -4 173 | icon.font.size: 16 174 | update_freq: 5 175 | padding_left: -58 176 | script: '{}/temp.nu' 177 | click_script: '{}/popups/temp.nu' 178 | popup.align: left 179 | popup.background.border_width: 2 180 | popup.background.corner_radius: 3 181 | popup.background.border_color: $colors.yellow 182 | popup.background.drawing: on 183 | } 184 | popups: [ 185 | { 186 | name: temp_fan1 187 | args: { 188 | label: "unk" 189 | icon: '󱑲' 190 | } 191 | } 192 | { 193 | name: temp_fan2 194 | args: { 195 | label: "unk" 196 | icon: '󱑳' 197 | } 198 | } 199 | { 200 | name: temp_power 201 | args: { 202 | label: "unk" 203 | icon: '󰠠' 204 | } 205 | } 206 | ] 207 | } 208 | { 209 | name: temp_gpu 210 | args: { 211 | label.font.size: 7 212 | padding_left: 0 213 | padding_right: 0 214 | icon.font.size: 16 215 | icon.drawing: off 216 | label.y_offset: 4 217 | background.drawing: off 218 | } 219 | } 220 | { 221 | name: network_down 222 | args: { 223 | icon: '󰖩' 224 | label.font.size: 7 225 | label.y_offset: -4 226 | update_freq: 3 227 | padding_left: -73 228 | padding_right: 23 229 | script: '{}/network.nu' 230 | icon.color: $colors.purple 231 | label.highlight_color: $colors.purple 232 | background.border_color: $colors.purple 233 | } 234 | } 235 | { 236 | name: network_up 237 | args: { 238 | label.font.size: 7 239 | padding_left: 0 240 | padding_right: 0 241 | icon.drawing: off 242 | label.y_offset: 4 243 | background.drawing: off 244 | label.highlight_color: $colors.purple 245 | } 246 | } 247 | ] 248 | } 249 | 250 | const table = [[a b]; [1 2] [2 [4 4]]] 251 | const table_no_row = [[a b]; ] 252 | -------------------------------------------------------------------------------- /test/expected_decl.nu: -------------------------------------------------------------------------------- 1 | # decl_extern 2 | export extern hello [name: string] { 3 | $"hello ($name)!" 4 | } 5 | # decl_extern no body block 6 | extern hi [ 7 | name: string 8 | --long (-s) # flags 9 | ] 10 | # env 11 | hide-env ABC 12 | with-env {ABC: 'hello'} { 13 | ( 14 | do -i --env {|foo, bar| 15 | print $env.ABC 16 | } 17 | foo bar 18 | ) 19 | } 20 | 21 | # closure 22 | let cls = {|foo bar baz| 23 | ( 24 | $foo + 25 | $bar + $baz 26 | ) 27 | } 28 | 29 | # decl_export 30 | export-env { 31 | $env.hello = 'hello' 32 | } 33 | 34 | # decl_def 35 | def "hi there" [where: string]: nothing -> record, bar: int> { 36 | { 37 | foo: [["baz"]; [1.0]] 38 | bar: 1 39 | } 40 | } 41 | 42 | # decl_use 43 | use greetings.nu hello 44 | export use greetings.nu * 45 | use module [ foo bar ] 46 | use module [ "foo" "bar" ] 47 | use module [ foo "bar" ] 48 | use module [ "foo" bar ] 49 | 50 | # decl_module 51 | module greetings { 52 | export def hello [name: string] { 53 | $"hello ($name)!" 54 | } 55 | 56 | export def hi [where: string] { 57 | $"hi ($where)!" 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /test/expected_exe.nu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nu 2 | 3 | use constants.nu [ 4 | colors 5 | get_icon_by_app_name 6 | "foo bar" 7 | ] 8 | 9 | const animate_frames = 30 10 | 11 | def modify_args_per_workspace [ 12 | sid: string 13 | focused_sid: string 14 | ]: nothing -> list { 15 | let icons = ( 16 | aerospace list-windows --workspace $sid --json 17 | | from json | get app-name 18 | | each { $in | get_icon_by_app_name } 19 | | uniq | sort 20 | | str join ' ' 21 | ) 22 | let extra = ( 23 | if $sid == $focused_sid { 24 | {highlight: on border_color: $colors.green} 25 | } else { 26 | {highlight: off border_color: $colors.fg} 27 | } 28 | ) 29 | 30 | ['--set' $"space.($sid)"] 31 | | append ( 32 | if (($icons | is-empty) and ($sid != $focused_sid)) { 33 | [ 34 | background.drawing=off 35 | label= 36 | padding_left=-2 37 | padding_right=-2 38 | ] 39 | } else { 40 | [ 41 | background.drawing=on 42 | label=($icons) 43 | label.highlight=($extra.highlight) 44 | padding_left=2 45 | padding_right=2 46 | ] 47 | } 48 | ) 49 | | append $"background.border_color=($extra.border_color)" 50 | } 51 | 52 | def workspace_modification_args [ 53 | name: string 54 | last_sid: string 55 | ]: nothing -> list { 56 | # use listener's label to store last focused space id 57 | let focused_sid = (aerospace list-workspaces --focused) 58 | let ids_to_modify = ( 59 | if ($last_sid | is-empty) { (aerospace list-workspaces --all | lines) } 60 | else { 61 | [$focused_sid $last_sid] 62 | } 63 | ) 64 | $ids_to_modify 65 | | uniq 66 | | each { modify_args_per_workspace $in $focused_sid } 67 | | flatten 68 | | append ["--set" $name $"label=($focused_sid)"] 69 | } 70 | 71 | # remained for other possible signals 72 | match $env.SENDER { 73 | _ => { 74 | # invisible item to store last focused sid 75 | let last_sid = (sketchybar --query $env.NAME | from json | get label.value) 76 | sketchybar --animate tanh $animate_frames ...(workspace_modification_args $env.NAME $last_sid) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/expected_keyword.nu: -------------------------------------------------------------------------------- 1 | overlay use -p --reload __trigger__ as __auto_venv 2 | overlay hide spam 3 | overlay list 4 | overlay new name 5 | overlay hide spam --keep-custom 6 | overlay hide --keep-env [PWD] new_name 7 | hide PWD 8 | hide foo bar 9 | hide-env PWD 10 | hide-env -i PWD PATH 11 | source-env foo.nu 12 | -------------------------------------------------------------------------------- /test/expected_string.nu: -------------------------------------------------------------------------------- 1 | const dir_preview_cmd = "eza --tree -L 3 --color=always {} | head -200" 2 | const file_preview_cmd = "bat -n --color=always --line-range :200 {}" 3 | const default_preview_cmd = "if ({} | path type) == 'dir'" + $' {($dir_preview_cmd)} else {($file_preview_cmd)}' 4 | # TODO: r#''#r verbatim 5 | const help_preview_cmd = "try {help {}} catch {'custom command or alias'}" 6 | const external_tldr_cmd = "try {tldr -C {}} catch {'No doc yet'}" 7 | const hybrid_help_cmd = ( 8 | "Multiline 9 | string" + 10 | ($external_tldr_cmd | str replace '{}' '(foo)') + 11 | "another multiline 12 | string" + 13 | ($help_preview_cmd | str replace '{}' '(bar)') 14 | ) 15 | mut foo = 'foo bar' 16 | $foo += 'baz' 17 | $foo += r#'baz'# 18 | -------------------------------------------------------------------------------- /test/input_attribute.nu: -------------------------------------------------------------------------------- 1 | @test 2 | def test [] {} 3 | @test; def test [] {} 4 | 5 | @example "adding some dummy paths to an empty PATH" { 6 | with-env { PATH: [] } { 7 | path add "returned" --ret 8 | } 9 | } --result [returned] 10 | @example "adding paths based on the operating system" { 11 | path add {linux: "foo"} 12 | }; export def --env "path add" [ 13 | --ret (-r) # return $env.PATH, useful in pipelines to avoid scoping. 14 | --append (-a) # append to $env.PATH instead of prepending to. 15 | p 16 | ] {} 17 | 18 | @search-terms multiply times 19 | @example "random" {2 | double}; @test; def double []: [number -> number] { $in * 2 } 20 | -------------------------------------------------------------------------------- /test/input_command.nu: -------------------------------------------------------------------------------- 1 | # local command 2 | ls | get -i name 3 | | length; ls # multiline command 4 | | length 5 | # external command 6 | ^git add (ls 7 | | get -i name) 8 | FOO=BAR BAR=BAZ ^$cmd --arg1=val1 -arg2 arg=value arg=($arg3) 9 | cat unknown.txt o+e> (null-device) 10 | 11 | $hash | $in + "\n" out>> $NUENV_FILE 12 | -------------------------------------------------------------------------------- /test/input_comment.nu: -------------------------------------------------------------------------------- 1 | # comment at the top 2 | 3 | # comment before command 4 | # multiline 5 | def foo_bar [ # comment at [ 6 | foo: string # comment for arg 7 | bar: int # another comment 8 | ] { # comment at { 9 | # comment in body 10 | [ 11 | foo # comment in list 12 | # another comment 13 | bar 14 | ]; 15 | { 16 | foo: foo # comment in record 17 | # another comment 18 | bar: bar 19 | } # comment at } 20 | ( # comment at ( 21 | foo # comment in parenthesis 22 | bar # another comment 23 | ) 24 | } 25 | 26 | # top level comment 27 | # multiline 28 | -------------------------------------------------------------------------------- /test/input_ctrl.nu: -------------------------------------------------------------------------------- 1 | # for 2 | for i in [1, 2, 3] { 3 | # if 4 | if (true or false) { 5 | print "break"; break # break 6 | } else if not false { 7 | print "continue"; continue # continue 8 | } 9 | return 1 # return 10 | } 11 | # alias 12 | alias ll = ls -l # alias comment 13 | # where 14 | ls | where $in.name == 'foo' 15 | | where {|e| $e.item.name !~ $'^($e.index + 1)' } 16 | # match 17 | let foo = { name: 'bar', count: 7 } 18 | match $foo { 19 | { name: 'bar' count: $it } if $it < 5 => ($it + 3), # match arm comment 20 | # match comment 21 | { name: 'bar' count: $it } if not ($it >= 5) => ($it + 7), 22 | _ => {exit 0} 23 | } 24 | # while 25 | mut x = 0; while $x < 10 { $x = $x + 1 }; $x # while comment 26 | # loop 27 | loop { if $x > 10 { break }; 28 | $x = $x + 1 } 29 | # try 30 | try { 31 | # error 32 | error make -u { 33 | msg: 'Some error info' }}; print 'Resuming' 34 | -------------------------------------------------------------------------------- /test/input_data.nu: -------------------------------------------------------------------------------- 1 | export const config = { 2 | default: { 3 | align: center 4 | updates: when_shown 5 | padding_left: 2 6 | padding_right: 2 7 | icon.font: "Sarasa Term SC Nerd:Bold:17.0" 8 | label.font: "Sarasa Term SC Nerd:Bold:12.0" 9 | icon.color: $colors.white 10 | label.color: $colors.fg 11 | icon.padding_left: 8 12 | icon.padding_right: 2 13 | label.padding_left: 2 14 | label.padding_right: 8 15 | background.corner_radius: 10 16 | background.color: $colors.bg 17 | background.border_width: 1 18 | background.border_color: $colors.bg 19 | } 20 | 21 | workspace_default_args: { 22 | icon.font.size: 12 23 | label.font.size: 17 24 | label.shadow.drawing: on 25 | label.shadow.color: $colors.bg 26 | label.shadow.distance: 3 27 | label.highlight_color: $colors.green 28 | background.drawing: off 29 | background.color: $colors.transparent 30 | background.border_width: 2 31 | background.border_color: $colors.fg 32 | background.shadow.drawing: on 33 | background.shadow.color: $colors.bg 34 | background.shadow.distance: 3 35 | } 36 | 37 | plugin_configs: [ 38 | { 39 | name: ws_listener 40 | pos: left 41 | events: [aerospace_workspace_change space_windows_change] 42 | args: { 43 | updates: on 44 | drawing: off 45 | script: '{}/aerospace.nu' 46 | } 47 | } 48 | { 49 | name: front_app 50 | pos: left 51 | events: [front_app_switched aerospace_mode_change] 52 | args: { 53 | label.color: $colors.black 54 | icon.color: $colors.black 55 | icon.font.size: 20 56 | background.color: $colors.blue 57 | background.corner_radius: 3 58 | background.shadow.drawing: on 59 | background.shadow.color: $colors.bg 60 | background.shadow.distance: 3 61 | script: '{}/front_app.nu' 62 | } 63 | } 64 | { 65 | name: media 66 | pos: center 67 | events: [media_change] 68 | args: { 69 | icon: '󰐎' 70 | icon.color: $colors.fg 71 | label.color: $colors.fg 72 | background.color: $colors.bg 73 | background.border_color: $colors.fg 74 | script: '{}/media.nu' 75 | } 76 | } 77 | { 78 | name: media_cover 79 | pos: center 80 | events: [media_change] 81 | args: { 82 | icon.drawing: off 83 | label.drawing: off 84 | background.image.drawing: on 85 | background.image: media.artwork 86 | background.image.scale: 0.7 87 | background.color: $colors.transparent 88 | background.border_width: 0 89 | } 90 | } 91 | { 92 | name: clock 93 | args: { 94 | update_freq: 30 95 | icon: '' 96 | script: '{}/clock.nu' 97 | background.corner_radius: 3 98 | padding_right: 1 99 | padding_left: 1 100 | label.font.size: 10 101 | } 102 | } 103 | { 104 | name: volume 105 | events: [volume_change] 106 | args: { 107 | script: '{}/volume.nu' 108 | background.corner_radius: 3 109 | padding_right: 1 110 | padding_left: 1 111 | } 112 | } 113 | { 114 | name: battery 115 | events: [system_woke power_source_change] 116 | args: { 117 | update_freq: 120 118 | script: '{}/battery.nu' 119 | click_script: 'open x-apple.systempreferences:com.apple.preference.battery' 120 | background.corner_radius: 3 121 | padding_right: 1 122 | } 123 | } 124 | { 125 | name: separator_right 126 | args: { 127 | icon: '' 128 | padding_left: 0 129 | label.drawing: off 130 | background.drawing: off 131 | click_script: '{}/toggle_stats.nu' 132 | } 133 | } 134 | { 135 | name: disk 136 | args: { 137 | icon: '' 138 | update_freq: 120 139 | script: '{}/disk.nu' 140 | click_script: 'open -a "Disk Utility"' 141 | icon.color: $colors.green 142 | background.border_color: $colors.green 143 | } 144 | } 145 | { 146 | name: cpu 147 | args: { 148 | icon: '' 149 | update_freq: 10 150 | script: '{}/cpu.nu' 151 | click_script: 'open -a "Activity Monitor"' 152 | icon.color: $colors.yellow 153 | background.border_color: $colors.yellow 154 | } 155 | } 156 | { 157 | name: memory 158 | args: { 159 | icon: '﬙' 160 | update_freq: 10 161 | script: '{}/mem.nu' 162 | click_script: 'open -a "Activity Monitor"' 163 | icon.color: $colors.cyan 164 | background.border_color: $colors.cyan 165 | } 166 | } 167 | { 168 | name: temp_cpu 169 | args: { 170 | icon: '' 171 | label.font.size: 7 172 | label.y_offset: -4 173 | icon.font.size: 16 174 | update_freq: 5 175 | padding_left: -58 176 | script: '{}/temp.nu' 177 | click_script: '{}/popups/temp.nu' 178 | popup.align: left 179 | popup.background.border_width: 2 180 | popup.background.corner_radius: 3 181 | popup.background.border_color: $colors.yellow 182 | popup.background.drawing: on 183 | } 184 | popups: [ 185 | { 186 | name: temp_fan1 187 | args: { 188 | label: "unk" 189 | icon: '󱑲' 190 | } 191 | } 192 | { 193 | name: temp_fan2 194 | args: { 195 | label: "unk" 196 | icon: '󱑳' 197 | } 198 | } 199 | { 200 | name: temp_power 201 | args: { 202 | label: "unk" 203 | icon: '󰠠' 204 | } 205 | } 206 | ] 207 | } 208 | { 209 | name: temp_gpu 210 | args: { 211 | label.font.size: 7 212 | padding_left: 0 213 | padding_right: 0 214 | icon.font.size: 16 215 | icon.drawing: off 216 | label.y_offset: 4 217 | background.drawing: off 218 | } 219 | } 220 | { 221 | name: network_down 222 | args: { 223 | icon: '󰖩' 224 | label.font.size: 7 225 | label.y_offset: -4 226 | update_freq: 3 227 | padding_left: -73 228 | padding_right: 23 229 | script: '{}/network.nu' 230 | icon.color: $colors.purple 231 | label.highlight_color: $colors.purple 232 | background.border_color: $colors.purple 233 | } 234 | } 235 | { 236 | name: network_up 237 | args: { 238 | label.font.size: 7 239 | padding_left: 0 240 | padding_right: 0 241 | icon.drawing: off 242 | label.y_offset: 4 243 | background.drawing: off 244 | label.highlight_color: $colors.purple 245 | } 246 | } 247 | ] 248 | } 249 | 250 | const table = [[a b]; [1 2] [2 [4 4]]] 251 | const table_no_row = [[a b];] 252 | -------------------------------------------------------------------------------- /test/input_decl.nu: -------------------------------------------------------------------------------- 1 | # decl_extern 2 | export extern hello [name: string] { 3 | $"hello ($name)!" 4 | } 5 | # decl_extern no body block 6 | extern hi [name: string --long (-s) # flags 7 | ] 8 | # env 9 | hide-env ABC 10 | with-env {ABC: 'hello'} { 11 | ( 12 | do -i --env {|foo, bar | print $env.ABC 13 | } 14 | foo bar 15 | ) 16 | } 17 | 18 | # closure 19 | let cls = {| foo bar baz| 20 | ( 21 | $foo + 22 | $bar + $baz 23 | ) 24 | } 25 | 26 | # decl_export 27 | export-env { 28 | $env.hello = 'hello' 29 | } 30 | 31 | # decl_def 32 | def "hi there" [where: string]: nothing -> record, bar: int> { 33 | {foo: [["baz"]; [1.0]], 34 | bar: 1} 35 | } 36 | 37 | # decl_use 38 | use greetings.nu hello 39 | export use greetings.nu * 40 | use module [foo bar] 41 | use module ["foo" "bar"] 42 | use module [foo "bar"] 43 | use module ["foo" bar] 44 | 45 | # decl_module 46 | module greetings { 47 | export def hello [name: string] { 48 | $"hello ($name)!" 49 | } 50 | 51 | export def hi [where: string] { 52 | $"hi ($where)!" 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /test/input_exe.nu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nu 2 | 3 | use constants.nu [ 4 | colors 5 | get_icon_by_app_name 6 | "foo bar" 7 | ] 8 | 9 | const animate_frames = 30 10 | 11 | def modify_args_per_workspace [ 12 | sid: string 13 | focused_sid: string 14 | ]: nothing -> list { 15 | let icons = (aerospace list-windows --workspace $sid --json 16 | | from json | get app-name 17 | | each { $in | get_icon_by_app_name } 18 | | uniq | sort 19 | | str join ' ') 20 | let extra = (if $sid == $focused_sid 21 | { 22 | { highlight: on border_color: $colors.green } 23 | } else { 24 | { highlight: off border_color: $colors.fg } 25 | }) 26 | 27 | ['--set' $"space.($sid)"] 28 | | append (if (($icons | is-empty) and ($sid != $focused_sid)) { 29 | [ 30 | background.drawing=off 31 | label= 32 | padding_left=-2 33 | padding_right=-2 34 | ] 35 | } else { 36 | [ 37 | background.drawing=on 38 | label=($icons) 39 | label.highlight=($extra.highlight) 40 | padding_left=2 41 | padding_right=2 42 | ] 43 | }) 44 | | append $"background.border_color=($extra.border_color)" 45 | } 46 | 47 | def workspace_modification_args [ 48 | name: string 49 | last_sid: string 50 | ]: nothing -> list { 51 | # use listener's label to store last focused space id 52 | let focused_sid = (aerospace list-workspaces --focused) 53 | let ids_to_modify = ( 54 | if ($last_sid | is-empty) 55 | {(aerospace list-workspaces --all | lines)} 56 | else { 57 | [$focused_sid $last_sid] 58 | }) 59 | $ids_to_modify 60 | | uniq 61 | | each { modify_args_per_workspace $in $focused_sid } 62 | | flatten 63 | | append ["--set" $name $"label=($focused_sid)"] 64 | } 65 | 66 | # remained for other possible signals 67 | match $env.SENDER { 68 | _ => { 69 | # invisible item to store last focused sid 70 | let last_sid = (sketchybar --query $env.NAME | from json | get label.value) 71 | sketchybar --animate tanh $animate_frames ...(workspace_modification_args $env.NAME $last_sid) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/input_keyword.nu: -------------------------------------------------------------------------------- 1 | overlay use -p --reload __trigger__ as __auto_venv 2 | overlay hide spam 3 | overlay list 4 | overlay new name 5 | overlay hide spam --keep-custom 6 | overlay hide --keep-env [PWD] new_name 7 | hide PWD 8 | hide foo bar 9 | hide-env PWD 10 | hide-env -i PWD PATH 11 | source-env foo.nu 12 | -------------------------------------------------------------------------------- /test/input_string.nu: -------------------------------------------------------------------------------- 1 | const dir_preview_cmd = "eza --tree -L 3 --color=always {} | head -200" 2 | const file_preview_cmd = "bat -n --color=always --line-range :200 {}" 3 | const default_preview_cmd = "if ({} | path type) == 'dir'" + $' {($dir_preview_cmd)} else {($file_preview_cmd)}' 4 | # TODO: r#''#r verbatim 5 | const help_preview_cmd = "try {help {}} catch {'custom command or alias'}" 6 | const external_tldr_cmd = "try {tldr -C {}} catch {'No doc yet'}" 7 | const hybrid_help_cmd = ("Multiline 8 | string" + 9 | ($external_tldr_cmd | str replace '{}' '(foo)') + 10 | "another multiline 11 | string" + 12 | ($help_preview_cmd | str replace '{}' '(bar)') 13 | ) 14 | mut foo = 'foo bar' 15 | $foo += 'baz' 16 | $foo += r#'baz'# 17 | -------------------------------------------------------------------------------- /toolkit.nu: -------------------------------------------------------------------------------- 1 | use std assert 2 | 3 | const script_pwd = path self . 4 | 5 | def run_ide_check [ 6 | file: path 7 | ] { 8 | nu --ide-check 9999 $file 9 | | lines 10 | | each { $in | from json } 11 | | flatten 12 | | where severity? == Error 13 | | reject start end 14 | } 15 | 16 | def print_progress [ 17 | ratio: float 18 | length: int = 20 19 | ] { 20 | let done = '▓' 21 | let empty = '░' 22 | let count = [1 (($ratio * $length) | into int)] | math max 23 | ( 24 | print -n 25 | ('' | fill -c $done -w $count) 26 | ('' | fill -c $empty -w ($length - $count)) 27 | ($ratio * 100 | into string --decimals 0) % 28 | ) 29 | } 30 | 31 | # Test the topiary formatter with all nu files within a directory 32 | # each script should pass the idempotent check as well as the linter 33 | export def test_format [ 34 | path: path # path to test 35 | ] { 36 | $env.TOPIARY_CONFIG_FILE = ($script_pwd | path join languages.ncl) 37 | $env.TOPIARY_LANGUAGE_DIR = ($script_pwd | path join languages) 38 | let files = if ($path | path type) == 'file' { 39 | [$path] 40 | } else { 41 | glob $'($path | str trim -r -c '/')/**/*.nu' 42 | } 43 | let len = $files | length 44 | if $len == 0 { 45 | print $"No nu scripts found in (ansi yellow)($path).(ansi reset)" 46 | return 47 | } 48 | let all_passed = 1..$len | par-each {|i| 49 | let file = $files | get ($i - 1) 50 | let target = $"(random uuid).nu" 51 | if ($i mod 10) == 0 { 52 | print -n $"(ansi -e '1000D')" 53 | print_progress ($i / $len) 54 | } 55 | let failed = try { 56 | cp $file $target 57 | let err_before = run_ide_check $target 58 | topiary format $target 59 | let err_after = run_ide_check $target 60 | assert ($err_before == $err_after) 61 | true 62 | } catch { 63 | print $"(ansi red)Error detected: (ansi yellow)($file)(ansi reset)" 64 | false 65 | } 66 | rm $target 67 | $failed 68 | } 69 | | all { $in } 70 | if $all_passed { 71 | print -n $"(ansi -e '1000D')" 72 | print $"(ansi green)All nu scripts successfully passed the check, but style issues are still possible.(ansi reset)" 73 | } 74 | } 75 | --------------------------------------------------------------------------------