Add --dump-format json (#992)

This commit is contained in:
Casey Rodarmor 2021-11-17 00:07:48 -08:00 committed by GitHub
parent 53d3c7569c
commit 0ae91884e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1106 additions and 138 deletions

87
Cargo.lock generated
View File

@ -1,7 +1,5 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -71,9 +69,9 @@ checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.71" version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -118,9 +116,9 @@ dependencies = [
[[package]] [[package]]
name = "ctrlc" name = "ctrlc"
version = "3.2.1" version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1"
dependencies = [ dependencies = [
"nix", "nix",
"winapi", "winapi",
@ -221,6 +219,12 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]] [[package]]
name = "just" name = "just"
version = "0.10.3" version = "0.10.3"
@ -242,6 +246,8 @@ dependencies = [
"log", "log",
"pretty_assertions", "pretty_assertions",
"regex", "regex",
"serde",
"serde_json",
"similar", "similar",
"snafu", "snafu",
"strum", "strum",
@ -269,9 +275,9 @@ checksum = "441225017b106b9f902e97947a6d31e44ebcf274b91bdbfb51e5c477fcd468e5"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.106" version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
@ -305,9 +311,9 @@ dependencies = [
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.23.0" version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cc", "cc",
@ -327,9 +333,9 @@ dependencies = [
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.15" version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]] [[package]]
name = "pretty_assertions" name = "pretty_assertions"
@ -369,18 +375,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.32" version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.10" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -481,6 +487,43 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "serde"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "similar" name = "similar"
version = "2.1.0" version = "2.1.0"
@ -520,9 +563,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "structopt" name = "structopt"
version = "0.3.25" version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa"
dependencies = [ dependencies = [
"clap", "clap",
"lazy_static", "lazy_static",
@ -531,9 +574,9 @@ dependencies = [
[[package]] [[package]]
name = "structopt-derive" name = "structopt-derive"
version = "0.4.18" version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-error", "proc-macro-error",
@ -565,9 +608,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.81" version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -19,6 +19,8 @@ members = [".", "bin/ref-type"]
ansi_term = "0.12.0" ansi_term = "0.12.0"
atty = "0.2.0" atty = "0.2.0"
camino = "1.0.4" camino = "1.0.4"
clap = { version = "2.33.0", features = ["wrap_help"] }
ctrlc = { version = "3.1.1", features = ["termination"] }
derivative = "2.0.0" derivative = "2.0.0"
dotenv = "0.15.0" dotenv = "0.15.0"
edit-distance = "2.0.0" edit-distance = "2.0.0"
@ -28,29 +30,17 @@ lexiclean = "0.0.1"
libc = "0.2.0" libc = "0.2.0"
log = "0.4.4" log = "0.4.4"
regex = "1.5.4" regex = "1.5.4"
serde = { version = "1.0.130", features = ["derive", "rc"] }
serde_json = "1.0.68"
similar = { version = "2.1.0", features = ["unicode"] }
snafu = "0.6.0" snafu = "0.6.0"
strum = { version = "0.22.0", features = ["derive"] }
strum_macros = "0.22.0" strum_macros = "0.22.0"
target = "2.0.0" target = "2.0.0"
tempfile = "3.0.0" tempfile = "3.0.0"
typed-arena = "2.0.1" typed-arena = "2.0.1"
unicode-width = "0.1.0" unicode-width = "0.1.0"
[dependencies.clap]
version = "2.33.0"
features = ["wrap_help"]
[dependencies.ctrlc]
version = "3.1.1"
features = ["termination"]
[dependencies.similar]
version = "2.1.0"
features = ["unicode"]
[dependencies.strum]
version = "0.22.0"
features = ["derive"]
[dev-dependencies] [dev-dependencies]
cradle = "0.2.0" cradle = "0.2.0"
executable-path = "1.0.0" executable-path = "1.0.0"

View File

@ -1730,6 +1730,10 @@ default:
echo foo echo foo
``` ```
=== Dumping Justfiles as JSON
The `--dump` command can be used with `--dump-format json` to print a JSON representation of a justfile. The JSON format is currently unstable, so the `--unstable` flag is required.
=== Changelog === Changelog
A changelog for the latest release is available in link:CHANGELOG.md[]. Changelogs for previous releases are available on https://github.com/casey/just/releases[the releases page]. `just --changelog` can also be used to make a `just` binary print its changelog. A changelog for the latest release is available in link:CHANGELOG.md[]. Changelogs for previous releases are available on https://github.com/casey/just/releases[the releases page]. `just --changelog` can also be used to make a `just` binary print its changelog.

View File

@ -20,7 +20,7 @@ _just() {
case "${cmd}" in case "${cmd}" in
just) just)
opts=" -q -u -v -e -l -h -V -f -d -c -s --check --dry-run --highlight --no-dotenv --no-highlight --quiet --shell-command --clear-shell-args --unsorted --unstable --verbose --changelog --choose --dump --edit --evaluate --fmt --init --list --summary --variables --help --version --chooser --color --list-heading --list-prefix --justfile --set --shell --shell-arg --working-directory --command --completions --show --dotenv-filename --dotenv-path <ARGUMENTS>... " opts=" -q -u -v -e -l -h -V -f -d -c -s --check --dry-run --highlight --no-dotenv --no-highlight --quiet --shell-command --clear-shell-args --unsorted --unstable --verbose --changelog --choose --dump --edit --evaluate --fmt --init --list --summary --variables --help --version --chooser --color --dump-format --list-heading --list-prefix --justfile --set --shell --shell-arg --working-directory --command --completions --show --dotenv-filename --dotenv-path <ARGUMENTS>... "
if [[ ${cur} == -* ]] ; then if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -41,6 +41,10 @@ _just() {
COMPREPLY=($(compgen -W "auto always never" -- "${cur}")) COMPREPLY=($(compgen -W "auto always never" -- "${cur}"))
return 0 return 0
;; ;;
--dump-format)
COMPREPLY=($(compgen -W "just json" -- "${cur}"))
return 0
;;
--list-heading) --list-heading)
COMPREPLY=($(compgen -f "${cur}")) COMPREPLY=($(compgen -f "${cur}"))
return 0 return 0

View File

@ -16,6 +16,7 @@ edit:completion:arg-completer[just] = [@words]{
&'just'= { &'just'= {
cand --chooser 'Override binary invoked by `--choose`' cand --chooser 'Override binary invoked by `--choose`'
cand --color 'Print colorful output' cand --color 'Print colorful output'
cand --dump-format 'Dump justfile as <FORMAT>'
cand --list-heading 'Print <TEXT> before list' cand --list-heading 'Print <TEXT> before list'
cand --list-prefix 'Print <TEXT> before each list item' cand --list-prefix 'Print <TEXT> before each list item'
cand -f 'Use <JUSTFILE> as justfile' cand -f 'Use <JUSTFILE> as justfile'
@ -48,7 +49,7 @@ edit:completion:arg-completer[just] = [@words]{
cand --verbose 'Use verbose output' cand --verbose 'Use verbose output'
cand --changelog 'Print changelog' cand --changelog 'Print changelog'
cand --choose 'Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`' cand --choose 'Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`'
cand --dump 'Print entire justfile' cand --dump 'Print justfile'
cand -e 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`' cand -e 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
cand --edit 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`' cand --edit 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
cand --evaluate 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.' cand --evaluate 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.'

View File

@ -11,6 +11,7 @@ complete -c just -a '(__fish_just_complete_recipes)'
# autogenerated completions # autogenerated completions
complete -c just -n "__fish_use_subcommand" -l chooser -d 'Override binary invoked by `--choose`' complete -c just -n "__fish_use_subcommand" -l chooser -d 'Override binary invoked by `--choose`'
complete -c just -n "__fish_use_subcommand" -l color -d 'Print colorful output' -r -f -a "auto always never" complete -c just -n "__fish_use_subcommand" -l color -d 'Print colorful output' -r -f -a "auto always never"
complete -c just -n "__fish_use_subcommand" -l dump-format -d 'Dump justfile as <FORMAT>' -r -f -a "just json"
complete -c just -n "__fish_use_subcommand" -l list-heading -d 'Print <TEXT> before list' complete -c just -n "__fish_use_subcommand" -l list-heading -d 'Print <TEXT> before list'
complete -c just -n "__fish_use_subcommand" -l list-prefix -d 'Print <TEXT> before each list item' complete -c just -n "__fish_use_subcommand" -l list-prefix -d 'Print <TEXT> before each list item'
complete -c just -n "__fish_use_subcommand" -s f -l justfile -d 'Use <JUSTFILE> as justfile' complete -c just -n "__fish_use_subcommand" -s f -l justfile -d 'Use <JUSTFILE> as justfile'
@ -36,7 +37,7 @@ complete -c just -n "__fish_use_subcommand" -l unstable -d 'Enable unstable feat
complete -c just -n "__fish_use_subcommand" -s v -l verbose -d 'Use verbose output' complete -c just -n "__fish_use_subcommand" -s v -l verbose -d 'Use verbose output'
complete -c just -n "__fish_use_subcommand" -l changelog -d 'Print changelog' complete -c just -n "__fish_use_subcommand" -l changelog -d 'Print changelog'
complete -c just -n "__fish_use_subcommand" -l choose -d 'Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`' complete -c just -n "__fish_use_subcommand" -l choose -d 'Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`'
complete -c just -n "__fish_use_subcommand" -l dump -d 'Print entire justfile' complete -c just -n "__fish_use_subcommand" -l dump -d 'Print justfile'
complete -c just -n "__fish_use_subcommand" -s e -l edit -d 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`' complete -c just -n "__fish_use_subcommand" -s e -l edit -d 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
complete -c just -n "__fish_use_subcommand" -l evaluate -d 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable\'s value.' complete -c just -n "__fish_use_subcommand" -l evaluate -d 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable\'s value.'
complete -c just -n "__fish_use_subcommand" -l fmt -d 'Format and overwrite justfile' complete -c just -n "__fish_use_subcommand" -l fmt -d 'Format and overwrite justfile'

View File

@ -21,6 +21,7 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
'just' { 'just' {
[CompletionResult]::new('--chooser', 'chooser', [CompletionResultType]::ParameterName, 'Override binary invoked by `--choose`') [CompletionResult]::new('--chooser', 'chooser', [CompletionResultType]::ParameterName, 'Override binary invoked by `--choose`')
[CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'Print colorful output') [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'Print colorful output')
[CompletionResult]::new('--dump-format', 'dump-format', [CompletionResultType]::ParameterName, 'Dump justfile as <FORMAT>')
[CompletionResult]::new('--list-heading', 'list-heading', [CompletionResultType]::ParameterName, 'Print <TEXT> before list') [CompletionResult]::new('--list-heading', 'list-heading', [CompletionResultType]::ParameterName, 'Print <TEXT> before list')
[CompletionResult]::new('--list-prefix', 'list-prefix', [CompletionResultType]::ParameterName, 'Print <TEXT> before each list item') [CompletionResult]::new('--list-prefix', 'list-prefix', [CompletionResultType]::ParameterName, 'Print <TEXT> before each list item')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Use <JUSTFILE> as justfile') [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Use <JUSTFILE> as justfile')
@ -53,7 +54,7 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Use verbose output') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Use verbose output')
[CompletionResult]::new('--changelog', 'changelog', [CompletionResultType]::ParameterName, 'Print changelog') [CompletionResult]::new('--changelog', 'changelog', [CompletionResultType]::ParameterName, 'Print changelog')
[CompletionResult]::new('--choose', 'choose', [CompletionResultType]::ParameterName, 'Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`') [CompletionResult]::new('--choose', 'choose', [CompletionResultType]::ParameterName, 'Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`')
[CompletionResult]::new('--dump', 'dump', [CompletionResultType]::ParameterName, 'Print entire justfile') [CompletionResult]::new('--dump', 'dump', [CompletionResultType]::ParameterName, 'Print justfile')
[CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`') [CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`')
[CompletionResult]::new('--edit', 'edit', [CompletionResultType]::ParameterName, 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`') [CompletionResult]::new('--edit', 'edit', [CompletionResultType]::ParameterName, 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`')
[CompletionResult]::new('--evaluate', 'evaluate', [CompletionResultType]::ParameterName, 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.') [CompletionResult]::new('--evaluate', 'evaluate', [CompletionResultType]::ParameterName, 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.')

View File

@ -17,6 +17,7 @@ _just() {
local common=( local common=(
'--chooser=[Override binary invoked by `--choose`]' \ '--chooser=[Override binary invoked by `--choose`]' \
'--color=[Print colorful output]: :(auto always never)' \ '--color=[Print colorful output]: :(auto always never)' \
'--dump-format=[Dump justfile as <FORMAT>]: :(just json)' \
'--list-heading=[Print <TEXT> before list]' \ '--list-heading=[Print <TEXT> before list]' \
'--list-prefix=[Print <TEXT> before each list item]' \ '--list-prefix=[Print <TEXT> before each list item]' \
'-f+[Use <JUSTFILE> as justfile]' \ '-f+[Use <JUSTFILE> as justfile]' \
@ -49,7 +50,7 @@ _just() {
'*--verbose[Use verbose output]' \ '*--verbose[Use verbose output]' \
'--changelog[Print changelog]' \ '--changelog[Print changelog]' \
'--choose[Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`]' \ '--choose[Select one or more recipes to run using a binary. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`]' \
'--dump[Print entire justfile]' \ '--dump[Print justfile]' \
'-e[Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`]' \ '-e[Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`]' \
'--edit[Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`]' \ '--edit[Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`]' \
'--evaluate[Evaluate and print all variables. If a variable name is given as an argument, only print that variable'\''s value.]' \ '--evaluate[Evaluate and print all variables. If a variable name is given as an argument, only print that variable'\''s value.]' \

View File

@ -1,9 +1,13 @@
use crate::common::*; use crate::common::*;
/// An alias, e.g. `name := target` /// An alias, e.g. `name := target`
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone, Serialize)]
pub(crate) struct Alias<'src, T = Rc<Recipe<'src>>> { pub(crate) struct Alias<'src, T = Rc<Recipe<'src>>> {
pub(crate) name: Name<'src>, pub(crate) name: Name<'src>,
#[serde(
bound(serialize = "T: Keyed<'src>"),
serialize_with = "keyed::serialize"
)]
pub(crate) target: T, pub(crate) target: T,
} }

View File

@ -81,6 +81,16 @@ impl<'src> Analyzer<'src> {
Ok(Justfile { Ok(Justfile {
warnings: ast.warnings, warnings: ast.warnings,
first: recipes
.values()
.fold(None, |accumulator, next| match accumulator {
None => Some(Rc::clone(next)),
Some(previous) => Some(if previous.line_number() < next.line_number() {
previous
} else {
Rc::clone(next)
}),
}),
aliases, aliases,
assignments, assignments,
recipes, recipes,

View File

@ -1,7 +1,7 @@
use crate::common::*; use crate::common::*;
/// A binding of `name` to `value` /// A binding of `name` to `value`
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Serialize)]
pub(crate) struct Binding<'src, V = String> { pub(crate) struct Binding<'src, V = String> {
/// Export binding as an environment variable to child processes /// Export binding as an environment variable to child processes
pub(crate) export: bool, pub(crate) export: bool,

View File

@ -27,6 +27,10 @@ pub(crate) use ::{
libc::EXIT_FAILURE, libc::EXIT_FAILURE,
log::{info, warn}, log::{info, warn},
regex::Regex, regex::Regex,
serde::{
ser::{SerializeMap, SerializeSeq},
Serialize, Serializer,
},
snafu::{ResultExt, Snafu}, snafu::{ResultExt, Snafu},
strum::{Display, EnumString, IntoStaticStr}, strum::{Display, EnumString, IntoStaticStr},
typed_arena::Arena, typed_arena::Arena,
@ -34,7 +38,7 @@ pub(crate) use ::{
}; };
// modules // modules
pub(crate) use crate::{completions, config, config_error, setting}; pub(crate) use crate::{completions, config, config_error, keyed, setting};
// functions // functions
pub(crate) use crate::{load_dotenv::load_dotenv, output::output, unindent::unindent}; pub(crate) use crate::{load_dotenv::load_dotenv, output::output, unindent::unindent};
@ -51,18 +55,18 @@ pub(crate) use crate::{
assignment_resolver::AssignmentResolver, ast::Ast, binding::Binding, color::Color, assignment_resolver::AssignmentResolver, ast::Ast, binding::Binding, color::Color,
compile_error::CompileError, compile_error_kind::CompileErrorKind, compile_error::CompileError, compile_error_kind::CompileErrorKind,
conditional_operator::ConditionalOperator, config::Config, config_error::ConfigError, conditional_operator::ConditionalOperator, config::Config, config_error::ConfigError,
count::Count, delimiter::Delimiter, dependency::Dependency, enclosure::Enclosure, error::Error, count::Count, delimiter::Delimiter, dependency::Dependency, dump_format::DumpFormat,
evaluator::Evaluator, expression::Expression, fragment::Fragment, function::Function, enclosure::Enclosure, error::Error, evaluator::Evaluator, expression::Expression,
function_context::FunctionContext, interrupt_guard::InterruptGuard, fragment::Fragment, function::Function, function_context::FunctionContext,
interrupt_handler::InterruptHandler, item::Item, justfile::Justfile, keyword::Keyword, interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
lexer::Lexer, line::Line, list::List, loader::Loader, name::Name, output_error::OutputError, justfile::Justfile, keyword::Keyword, lexer::Lexer, line::Line, list::List, loader::Loader,
parameter::Parameter, parameter_kind::ParameterKind, parser::Parser, platform::Platform, name::Name, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind,
position::Position, positional::Positional, recipe::Recipe, recipe_context::RecipeContext, parser::Parser, platform::Platform, position::Position, positional::Positional, recipe::Recipe,
recipe_resolver::RecipeResolver, scope::Scope, search::Search, search_config::SearchConfig, recipe_context::RecipeContext, recipe_resolver::RecipeResolver, scope::Scope, search::Search,
search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang, search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting,
show_whitespace::ShowWhitespace, string_kind::StringKind, string_literal::StringLiteral, settings::Settings, shebang::Shebang, show_whitespace::ShowWhitespace, string_kind::StringKind,
subcommand::Subcommand, suggestion::Suggestion, table::Table, thunk::Thunk, token::Token, string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table,
token_kind::TokenKind, unresolved_dependency::UnresolvedDependency, thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables, unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
verbosity::Verbosity, warning::Warning, verbosity::Verbosity, warning::Warning,
}; };

View File

@ -14,9 +14,12 @@ pub(crate) const DEFAULT_SHELL_ARG: &str = "-cu";
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub(crate) struct Config { pub(crate) struct Config {
pub(crate) color: Color,
pub(crate) check: bool, pub(crate) check: bool,
pub(crate) color: Color,
pub(crate) dotenv_filename: Option<String>,
pub(crate) dotenv_path: Option<PathBuf>,
pub(crate) dry_run: bool, pub(crate) dry_run: bool,
pub(crate) dump_format: DumpFormat,
pub(crate) highlight: bool, pub(crate) highlight: bool,
pub(crate) invocation_directory: PathBuf, pub(crate) invocation_directory: PathBuf,
pub(crate) list_heading: String, pub(crate) list_heading: String,
@ -30,8 +33,6 @@ pub(crate) struct Config {
pub(crate) subcommand: Subcommand, pub(crate) subcommand: Subcommand,
pub(crate) unsorted: bool, pub(crate) unsorted: bool,
pub(crate) unstable: bool, pub(crate) unstable: bool,
pub(crate) dotenv_filename: Option<String>,
pub(crate) dotenv_path: Option<PathBuf>,
pub(crate) verbosity: Verbosity, pub(crate) verbosity: Verbosity,
} }
@ -86,7 +87,10 @@ mod arg {
pub(crate) const CHOOSER: &str = "CHOOSER"; pub(crate) const CHOOSER: &str = "CHOOSER";
pub(crate) const CLEAR_SHELL_ARGS: &str = "CLEAR-SHELL-ARGS"; pub(crate) const CLEAR_SHELL_ARGS: &str = "CLEAR-SHELL-ARGS";
pub(crate) const COLOR: &str = "COLOR"; pub(crate) const COLOR: &str = "COLOR";
pub(crate) const DOTENV_FILENAME: &str = "DOTENV-FILENAME";
pub(crate) const DOTENV_PATH: &str = "DOTENV-PATH";
pub(crate) const DRY_RUN: &str = "DRY-RUN"; pub(crate) const DRY_RUN: &str = "DRY-RUN";
pub(crate) const DUMP_FORMAT: &str = "DUMP-FORMAT";
pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT"; pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT";
pub(crate) const JUSTFILE: &str = "JUSTFILE"; pub(crate) const JUSTFILE: &str = "JUSTFILE";
pub(crate) const LIST_HEADING: &str = "LIST-HEADING"; pub(crate) const LIST_HEADING: &str = "LIST-HEADING";
@ -100,8 +104,6 @@ mod arg {
pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND"; pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND";
pub(crate) const UNSORTED: &str = "UNSORTED"; pub(crate) const UNSORTED: &str = "UNSORTED";
pub(crate) const UNSTABLE: &str = "UNSTABLE"; pub(crate) const UNSTABLE: &str = "UNSTABLE";
pub(crate) const DOTENV_FILENAME: &str = "DOTENV_FILENAME";
pub(crate) const DOTENV_PATH: &str = "DOTENV_PATH";
pub(crate) const VERBOSE: &str = "VERBOSE"; pub(crate) const VERBOSE: &str = "VERBOSE";
pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY"; pub(crate) const WORKING_DIRECTORY: &str = "WORKING-DIRECTORY";
@ -109,6 +111,10 @@ mod arg {
pub(crate) const COLOR_AUTO: &str = "auto"; pub(crate) const COLOR_AUTO: &str = "auto";
pub(crate) const COLOR_NEVER: &str = "never"; pub(crate) const COLOR_NEVER: &str = "never";
pub(crate) const COLOR_VALUES: &[&str] = &[COLOR_AUTO, COLOR_ALWAYS, COLOR_NEVER]; pub(crate) const COLOR_VALUES: &[&str] = &[COLOR_AUTO, COLOR_ALWAYS, COLOR_NEVER];
pub(crate) const DUMP_FORMAT_JSON: &str = "json";
pub(crate) const DUMP_FORMAT_JUST: &str = "just";
pub(crate) const DUMP_FORMAT_VALUES: &[&str] = &[DUMP_FORMAT_JUST, DUMP_FORMAT_JSON];
} }
impl Config { impl Config {
@ -144,6 +150,15 @@ impl Config {
.help("Print what just would do without doing it") .help("Print what just would do without doing it")
.conflicts_with(arg::QUIET), .conflicts_with(arg::QUIET),
) )
.arg(
Arg::with_name(arg::DUMP_FORMAT)
.long("dump-format")
.takes_value(true)
.possible_values(arg::DUMP_FORMAT_VALUES)
.default_value(arg::DUMP_FORMAT_JUST)
.value_name("FORMAT")
.help("Dump justfile as <FORMAT>"),
)
.arg( .arg(
Arg::with_name(arg::HIGHLIGHT) Arg::with_name(arg::HIGHLIGHT)
.long("highlight") .long("highlight")
@ -283,7 +298,7 @@ impl Config {
.arg( .arg(
Arg::with_name(cmd::DUMP) Arg::with_name(cmd::DUMP)
.long("dump") .long("dump")
.help("Print entire justfile"), .help("Print justfile"),
) )
.arg( .arg(
Arg::with_name(cmd::EDIT) Arg::with_name(cmd::EDIT)
@ -367,7 +382,13 @@ impl Config {
} }
} }
fn color_from_value(value: &str) -> ConfigResult<Color> { fn color_from_matches(matches: &ArgMatches) -> ConfigResult<Color> {
let value = matches
.value_of(arg::COLOR)
.ok_or_else(|| ConfigError::Internal {
message: "`--color` had no value".to_string(),
})?;
match value { match value {
arg::COLOR_AUTO => Ok(Color::auto()), arg::COLOR_AUTO => Ok(Color::auto()),
arg::COLOR_ALWAYS => Ok(Color::always()), arg::COLOR_ALWAYS => Ok(Color::always()),
@ -378,6 +399,22 @@ impl Config {
} }
} }
fn dump_format_from_matches(matches: &ArgMatches) -> ConfigResult<DumpFormat> {
let value = matches
.value_of(arg::DUMP_FORMAT)
.ok_or_else(|| ConfigError::Internal {
message: "`--dump-format` had no value".to_string(),
})?;
match value {
arg::DUMP_FORMAT_JSON => Ok(DumpFormat::Json),
arg::DUMP_FORMAT_JUST => Ok(DumpFormat::Just),
_ => Err(ConfigError::Internal {
message: format!("Invalid argument `{}` to --dump-format.", value),
}),
}
}
pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult<Self> { pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult<Self> {
let invocation_directory = env::current_dir().context(config_error::CurrentDir)?; let invocation_directory = env::current_dir().context(config_error::CurrentDir)?;
@ -387,11 +424,7 @@ impl Config {
Verbosity::from_flag_occurrences(matches.occurrences_of(arg::VERBOSE)) Verbosity::from_flag_occurrences(matches.occurrences_of(arg::VERBOSE))
}; };
let color = Self::color_from_value( let color = Self::color_from_matches(matches)?;
matches
.value_of(arg::COLOR)
.expect("`--color` had no value"),
)?;
let set_count = matches.occurrences_of(arg::SET); let set_count = matches.occurrences_of(arg::SET);
let mut overrides = BTreeMap::new(); let mut overrides = BTreeMap::new();
@ -542,6 +575,7 @@ impl Config {
Ok(Self { Ok(Self {
check: matches.is_present(arg::CHECK), check: matches.is_present(arg::CHECK),
dry_run: matches.is_present(arg::DRY_RUN), dry_run: matches.is_present(arg::DRY_RUN),
dump_format: Self::dump_format_from_matches(matches)?,
highlight: !matches.is_present(arg::NO_HIGHLIGHT), highlight: !matches.is_present(arg::NO_HIGHLIGHT),
shell: matches.value_of(arg::SHELL).unwrap().to_owned(), shell: matches.value_of(arg::SHELL).unwrap().to_owned(),
load_dotenv: !matches.is_present(arg::NO_DOTENV), load_dotenv: !matches.is_present(arg::NO_DOTENV),
@ -612,7 +646,7 @@ FLAGS:
`fzf` `fzf`
--clear-shell-args Clear shell arguments --clear-shell-args Clear shell arguments
--dry-run Print what just would do without doing it --dry-run Print what just would do without doing it
--dump Print entire justfile --dump Print justfile
-e, --edit Edit justfile with editor given by $VISUAL or -e, --edit Edit justfile with editor given by $VISUAL or
$EDITOR, falling back to `vim` $EDITOR, falling back to `vim`
--evaluate Evaluate and print all variables. If a variable --evaluate Evaluate and print all variables. If a variable
@ -646,12 +680,15 @@ OPTIONS:
--completions <SHELL> --completions <SHELL>
Print shell completion script for <SHELL> [possible values: zsh, Print shell completion script for <SHELL> [possible values: zsh,
bash, fish, powershell, elvish] bash, fish, powershell, elvish]
--dotenv-filename <DOTENV_FILENAME> --dotenv-filename <DOTENV-FILENAME>
Search for environment file named <DOTENV-FILENAME> instead of Search for environment file named <DOTENV-FILENAME> instead of
`.env` `.env`
--dotenv-path <DOTENV_PATH> --dotenv-path <DOTENV-PATH>
Load environment file at <DOTENV-PATH> instead of searching for one Load environment file at <DOTENV-PATH> instead of searching for one
--dump-format <FORMAT>
Dump justfile as <FORMAT> [default: just] [possible values: just,
json]
-f, --justfile <JUSTFILE> Use <JUSTFILE> as justfile -f, --justfile <JUSTFILE> Use <JUSTFILE> as justfile
--list-heading <TEXT> Print <TEXT> before list --list-heading <TEXT> Print <TEXT> before list
--list-prefix <TEXT> --list-prefix <TEXT>
@ -693,6 +730,7 @@ ARGS:
args: [$($arg:expr),*], args: [$($arg:expr),*],
$(color: $color:expr,)? $(color: $color:expr,)?
$(dry_run: $dry_run:expr,)? $(dry_run: $dry_run:expr,)?
$(dump_format: $dump_format:expr,)?
$(highlight: $highlight:expr,)? $(highlight: $highlight:expr,)?
$(search_config: $search_config:expr,)? $(search_config: $search_config:expr,)?
$(shell: $shell:expr,)? $(shell: $shell:expr,)?
@ -712,6 +750,7 @@ ARGS:
let want = Config { let want = Config {
$(color: $color,)? $(color: $color,)?
$(dry_run: $dry_run,)? $(dry_run: $dry_run,)?
$(dump_format: $dump_format,)?
$(highlight: $highlight,)? $(highlight: $highlight,)?
$(search_config: $search_config,)? $(search_config: $search_config,)?
$(shell: $shell.to_owned(),)? $(shell: $shell.to_owned(),)?
@ -1101,6 +1140,12 @@ ARGS:
subcommand: Subcommand::Dump, subcommand: Subcommand::Dump,
} }
test! {
name: dump_format,
args: ["--dump-format", "json"],
dump_format: DumpFormat::Json,
}
test! { test! {
name: subcommand_edit, name: subcommand_edit,
args: ["--edit"], args: ["--edit"],

View File

@ -1,9 +1,10 @@
use crate::common::*; use crate::common::*;
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug, Serialize)]
pub(crate) struct Dependency<'src> { pub(crate) struct Dependency<'src> {
pub(crate) recipe: Rc<Recipe<'src>>,
pub(crate) arguments: Vec<Expression<'src>>, pub(crate) arguments: Vec<Expression<'src>>,
#[serde(serialize_with = "keyed::serialize")]
pub(crate) recipe: Rc<Recipe<'src>>,
} }
impl<'src> Display for Dependency<'src> { impl<'src> Display for Dependency<'src> {

5
src/dump_format.rs Normal file
View File

@ -0,0 +1,5 @@
#[derive(Debug, PartialEq)]
pub(crate) enum DumpFormat {
Json,
Just,
}

View File

@ -63,6 +63,9 @@ pub(crate) enum Error<'src> {
Dotenv { Dotenv {
dotenv_error: dotenv::Error, dotenv_error: dotenv::Error,
}, },
DumpJson {
serde_json_error: serde_json::Error,
},
EditorInvoke { EditorInvoke {
editor: OsString, editor: OsString,
io_error: io::Error, io_error: io::Error,
@ -434,6 +437,9 @@ impl<'src> ColorDisplay for Error<'src> {
Dotenv { dotenv_error } => { Dotenv { dotenv_error } => {
write!(f, "Failed to load environment file: {}", dotenv_error)?; write!(f, "Failed to load environment file: {}", dotenv_error)?;
} }
DumpJson { serde_json_error } => {
write!(f, "Failed to dump JSON to stdout: {}", serde_json_error)?;
}
EditorInvoke { editor, io_error } => { EditorInvoke { editor, io_error } => {
write!( write!(
f, f,

View File

@ -65,3 +65,51 @@ impl<'src> Display for Expression<'src> {
} }
} }
} }
impl<'src> Serialize for Expression<'src> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Backtick { contents, .. } => {
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element("evaluate")?;
seq.serialize_element(contents)?;
seq.end()
}
Self::Call { thunk } => thunk.serialize(serializer),
Self::Concatination { lhs, rhs } => {
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element("concatinate")?;
seq.serialize_element(lhs)?;
seq.serialize_element(rhs)?;
seq.end()
}
Self::Conditional {
lhs,
rhs,
then,
otherwise,
operator,
} => {
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element("if")?;
seq.serialize_element(&operator.to_string())?;
seq.serialize_element(lhs)?;
seq.serialize_element(rhs)?;
seq.serialize_element(then)?;
seq.serialize_element(otherwise)?;
seq.end()
}
Self::Group { contents } => contents.serialize(serializer),
Self::StringLiteral { string_literal } => string_literal.serialize(serializer),
Self::Variable { name } => {
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element("variable")?;
seq.serialize_element(name)?;
seq.end()
}
}
}
}

View File

@ -8,3 +8,19 @@ pub(crate) enum Fragment<'src> {
/// …an interpolation containing `expression`. /// …an interpolation containing `expression`.
Interpolation { expression: Expression<'src> }, Interpolation { expression: Expression<'src> },
} }
impl<'src> Serialize for Fragment<'src> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Self::Text { token } => serializer.serialize_str(token.lexeme()),
Self::Interpolation { expression } => {
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element(expression)?;
seq.end()
}
}
}
}

View File

@ -1,29 +1,19 @@
use crate::common::*; use crate::common::*;
#[derive(Debug, PartialEq)] use serde::Serialize;
#[derive(Debug, PartialEq, Serialize)]
pub(crate) struct Justfile<'src> { pub(crate) struct Justfile<'src> {
pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>,
pub(crate) assignments: Table<'src, Assignment<'src>>,
pub(crate) aliases: Table<'src, Alias<'src>>, pub(crate) aliases: Table<'src, Alias<'src>>,
pub(crate) assignments: Table<'src, Assignment<'src>>,
#[serde(serialize_with = "keyed::serialize_option")]
pub(crate) first: Option<Rc<Recipe<'src>>>,
pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>,
pub(crate) settings: Settings<'src>, pub(crate) settings: Settings<'src>,
pub(crate) warnings: Vec<Warning>, pub(crate) warnings: Vec<Warning>,
} }
impl<'src> Justfile<'src> { impl<'src> Justfile<'src> {
pub(crate) fn first(&self) -> Option<&Recipe<'src>> {
let mut first: Option<&Recipe<Dependency>> = None;
for recipe in self.recipes.values() {
if let Some(first_recipe) = first {
if recipe.line_number() < first_recipe.line_number() {
first = Some(recipe);
}
} else {
first = Some(recipe);
}
}
first
}
pub(crate) fn count(&self) -> usize { pub(crate) fn count(&self) -> usize {
self.recipes.len() self.recipes.len()
} }
@ -206,7 +196,7 @@ impl<'src> Justfile<'src> {
let argvec: Vec<&str> = if !arguments.is_empty() { let argvec: Vec<&str> = if !arguments.is_empty() {
arguments.iter().map(String::as_str).collect() arguments.iter().map(String::as_str).collect()
} else if let Some(recipe) = self.first() { } else if let Some(recipe) = &self.first {
let min_arguments = recipe.min_arguments(); let min_arguments = recipe.min_arguments();
if min_arguments > 0 { if min_arguments > 0 {
return Err(Error::DefaultRecipeRequiresArguments { return Err(Error::DefaultRecipeRequiresArguments {

View File

@ -9,3 +9,25 @@ impl<'key, T: Keyed<'key>> Keyed<'key> for Rc<T> {
self.as_ref().key() self.as_ref().key()
} }
} }
pub(crate) fn serialize<'src, S, K>(keyed: &K, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
K: Keyed<'src>,
{
serializer.serialize_str(&keyed.key())
}
pub(crate) fn serialize_option<'src, S, K>(
recipe: &Option<K>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
K: Keyed<'src>,
{
match recipe {
None => serializer.serialize_none(),
Some(keyed) => serialize(keyed, serializer),
}
}

View File

@ -12,6 +12,12 @@
clippy::wildcard_imports clippy::wildcard_imports
)] )]
pub use crate::run::run;
// Used in integration tests.
#[doc(hidden)]
pub use unindent::unindent;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
@ -29,6 +35,12 @@ pub mod node;
#[cfg(fuzzing)] #[cfg(fuzzing)]
pub(crate) mod fuzzing; pub(crate) mod fuzzing;
// Used by Janus, https://github.com/casey/janus, a tool
// that analyses all public justfiles on GitHub to avoid
// breaking changes.
#[doc(hidden)]
pub mod summary;
mod alias; mod alias;
mod analyzer; mod analyzer;
mod assignment; mod assignment;
@ -49,6 +61,7 @@ mod config_error;
mod count; mod count;
mod delimiter; mod delimiter;
mod dependency; mod dependency;
mod dump_format;
mod enclosure; mod enclosure;
mod error; mod error;
mod evaluator; mod evaluator;
@ -107,15 +120,3 @@ mod use_color;
mod variables; mod variables;
mod verbosity; mod verbosity;
mod warning; mod warning;
pub use crate::run::run;
// Used in integration tests.
#[doc(hidden)]
pub use unindent::unindent;
// Used by Janus, https://github.com/casey/janus, a tool
// that analyses all public justfiles on GitHub to avoid
// breaking changes.
#[doc(hidden)]
pub mod summary;

View File

@ -1,7 +1,8 @@
use crate::common::*; use crate::common::*;
/// A single line in a recipe body, consisting of any number of `Fragment`s. /// A single line in a recipe body, consisting of any number of `Fragment`s.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub(crate) struct Line<'src> { pub(crate) struct Line<'src> {
pub(crate) fragments: Vec<Fragment<'src>>, pub(crate) fragments: Vec<Fragment<'src>>,
} }

View File

@ -50,3 +50,12 @@ impl Display for Name<'_> {
write!(f, "{}", self.lexeme()) write!(f, "{}", self.lexeme())
} }
} }
impl<'src> Serialize for Name<'src> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.lexeme())
}
}

View File

@ -1,16 +1,16 @@
use crate::common::*; use crate::common::*;
/// A single function parameter /// A single function parameter
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Serialize)]
pub(crate) struct Parameter<'src> { pub(crate) struct Parameter<'src> {
/// The parameter name
pub(crate) name: Name<'src>,
/// The kind of parameter
pub(crate) kind: ParameterKind,
/// An optional default expression /// An optional default expression
pub(crate) default: Option<Expression<'src>>, pub(crate) default: Option<Expression<'src>>,
/// Export parameter as environment variable /// Export parameter as environment variable
pub(crate) export: bool, pub(crate) export: bool,
/// The kind of parameter
pub(crate) kind: ParameterKind,
/// The parameter name
pub(crate) name: Name<'src>,
} }
impl<'src> ColorDisplay for Parameter<'src> { impl<'src> ColorDisplay for Parameter<'src> {

View File

@ -1,7 +1,8 @@
use crate::common::*; use crate::common::*;
/// Parameters can either be… /// Parameters can either be…
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum ParameterKind { pub(crate) enum ParameterKind {
/// …singular, accepting a single argument /// …singular, accepting a single argument
Singular, Singular,

View File

@ -19,17 +19,17 @@ fn error_from_signal(recipe: &str, line_number: Option<usize>, exit_status: Exit
} }
/// A recipe, e.g. `foo: bar baz` /// A recipe, e.g. `foo: bar baz`
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Serialize)]
pub(crate) struct Recipe<'src, D = Dependency<'src>> { pub(crate) struct Recipe<'src, D = Dependency<'src>> {
pub(crate) body: Vec<Line<'src>>, pub(crate) body: Vec<Line<'src>>,
pub(crate) dependencies: Vec<D>, pub(crate) dependencies: Vec<D>,
pub(crate) doc: Option<&'src str>, pub(crate) doc: Option<&'src str>,
pub(crate) name: Name<'src>, pub(crate) name: Name<'src>,
pub(crate) parameters: Vec<Parameter<'src>>, pub(crate) parameters: Vec<Parameter<'src>>,
pub(crate) priors: usize,
pub(crate) private: bool, pub(crate) private: bool,
pub(crate) quiet: bool, pub(crate) quiet: bool,
pub(crate) shebang: bool, pub(crate) shebang: bool,
pub(crate) priors: usize,
} }
impl<'src, D> Recipe<'src, D> { impl<'src, D> Recipe<'src, D> {
@ -302,12 +302,6 @@ impl<'src, D> Recipe<'src, D> {
} }
} }
impl<'src, D> Keyed<'src> for Recipe<'src, D> {
fn key(&self) -> &'src str {
self.name.lexeme()
}
}
impl<'src, D: Display> ColorDisplay for Recipe<'src, D> { impl<'src, D: Display> ColorDisplay for Recipe<'src, D> {
fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> {
if let Some(doc) = self.doc { if let Some(doc) = self.doc {
@ -353,3 +347,9 @@ impl<'src, D: Display> ColorDisplay for Recipe<'src, D> {
Ok(()) Ok(())
} }
} }
impl<'src, D> Keyed<'src> for Recipe<'src, D> {
fn key(&self) -> &'src str {
self.name.lexeme()
}
}

View File

@ -8,10 +8,10 @@ pub(crate) enum Setting<'src> {
Shell(Shell<'src>), Shell(Shell<'src>),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Serialize)]
pub(crate) struct Shell<'src> { pub(crate) struct Shell<'src> {
pub(crate) command: StringLiteral<'src>,
pub(crate) arguments: Vec<StringLiteral<'src>>, pub(crate) arguments: Vec<StringLiteral<'src>>,
pub(crate) command: StringLiteral<'src>,
} }
impl<'src> Display for Setting<'src> { impl<'src> Display for Setting<'src> {

View File

@ -1,6 +1,6 @@
use crate::common::*; use crate::common::*;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Serialize)]
pub(crate) struct Settings<'src> { pub(crate) struct Settings<'src> {
pub(crate) dotenv_load: Option<bool>, pub(crate) dotenv_load: Option<bool>,
pub(crate) export: bool, pub(crate) export: bool,

View File

@ -18,3 +18,12 @@ impl Display for StringLiteral<'_> {
) )
} }
} }
impl<'src> Serialize for StringLiteral<'src> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.cooked)
}
}

View File

@ -76,7 +76,7 @@ impl Subcommand {
Command { overrides, .. } | Evaluate { overrides, .. } => { Command { overrides, .. } | Evaluate { overrides, .. } => {
justfile.run(config, &search, overrides, &[])? justfile.run(config, &search, overrides, &[])?
} }
Dump => Self::dump(ast), Dump => Self::dump(config, ast, justfile)?,
Format => Self::format(config, &search, &src, ast)?, Format => Self::format(config, &search, &src, ast)?,
List => Self::list(config, justfile), List => Self::list(config, justfile),
Run { Run {
@ -229,8 +229,17 @@ impl Subcommand {
Ok(()) Ok(())
} }
fn dump(ast: Ast) { fn dump(config: &Config, ast: Ast, justfile: Justfile) -> Result<(), Error<'static>> {
print!("{}", ast); match config.dump_format {
DumpFormat::Json => {
config.require_unstable("The JSON dump format is currently unstable.")?;
serde_json::to_writer(io::stdout(), &justfile)
.map_err(|serde_json_error| Error::DumpJson { serde_json_error })?;
println!();
}
DumpFormat::Just => print!("{}", ast),
}
Ok(())
} }
fn edit(search: &Search) -> Result<(), Error<'static>> { fn edit(search: &Search) -> Result<(), Error<'static>> {

View File

@ -2,7 +2,8 @@ use crate::common::*;
use std::collections::btree_map; use std::collections::btree_map;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Serialize)]
#[serde(transparent)]
pub(crate) struct Table<'key, V: Keyed<'key>> { pub(crate) struct Table<'key, V: Keyed<'key>> {
map: BTreeMap<&'key str, V>, map: BTreeMap<&'key str, V>,
} }

View File

@ -35,6 +35,16 @@ pub(crate) enum Thunk<'src> {
} }
impl<'src> Thunk<'src> { impl<'src> Thunk<'src> {
fn name(&self) -> &Name<'src> {
match self {
Self::Nullary { name, .. }
| Self::Unary { name, .. }
| Self::Binary { name, .. }
| Self::BinaryPlus { name, .. }
| Self::Ternary { name, .. } => name,
}
}
pub(crate) fn resolve( pub(crate) fn resolve(
name: Name<'src>, name: Name<'src>,
mut arguments: Vec<Expression<'src>>, mut arguments: Vec<Expression<'src>>,
@ -120,3 +130,34 @@ impl Display for Thunk<'_> {
} }
} }
} }
impl<'src> Serialize for Thunk<'src> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element("call")?;
seq.serialize_element(self.name())?;
match self {
Self::Nullary { .. } => {}
Self::Unary { arg, .. } => seq.serialize_element(&arg)?,
Self::Binary { args, .. } => {
for arg in args {
seq.serialize_element(arg)?;
}
}
Self::BinaryPlus { args, .. } => {
for arg in args.0.iter().map(Box::as_ref).chain(&args.1) {
seq.serialize_element(arg)?;
}
}
Self::Ternary { args, .. } => {
for arg in args {
seq.serialize_element(arg)?;
}
}
}
seq.end()
}
}

View File

@ -54,3 +54,16 @@ See https://github.com/casey/just/issues/469 for more details.")?;
Ok(()) Ok(())
} }
} }
impl Serialize for Warning {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("message", &self.color_display(Color::never()).to_string())?;
map.end()
}
}

View File

@ -31,7 +31,7 @@ test! {
error: The argument '--command <COMMAND>' requires a value but none was supplied error: The argument '--command <COMMAND>' requires a value but none was supplied
USAGE: USAGE:
just{} --color <COLOR> --shell <SHELL> --shell-arg <SHELL-ARG>... \ just{} --color <COLOR> --dump-format <FORMAT> --shell <SHELL> --shell-arg <SHELL-ARG>... \
<--changelog|--choose|--command <COMMAND>|--completions <SHELL>|--dump|--edit|\ <--changelog|--choose|--command <COMMAND>|--completions <SHELL>|--dump|--edit|\
--evaluate|--fmt|--init|--list|--show <RECIPE>|--summary|--variables> --evaluate|--fmt|--init|--list|--show <RECIPE>|--summary|--variables>

View File

@ -11,16 +11,19 @@ pub(crate) use std::{
str, str,
}; };
pub(crate) use cradle::input::Input; pub(crate) use ::{
pub(crate) use executable_path::executable_path; cradle::input::Input,
pub(crate) use just::unindent; executable_path::executable_path,
pub(crate) use libc::{EXIT_FAILURE, EXIT_SUCCESS}; just::unindent,
pub(crate) use pretty_assertions::Comparison; libc::{EXIT_FAILURE, EXIT_SUCCESS},
pub(crate) use regex::Regex; pretty_assertions::Comparison,
pub(crate) use tempfile::TempDir; regex::Regex,
pub(crate) use temptree::{temptree, tree, Tree}; serde_json::{json, Value},
pub(crate) use which::which; tempfile::TempDir,
pub(crate) use yaml_rust::YamlLoader; temptree::{temptree, tree, Tree},
which::which,
yaml_rust::YamlLoader,
};
pub(crate) use crate::{ pub(crate) use crate::{
assert_stdout::assert_stdout, assert_success::assert_success, tempdir::tempdir, test::Test, assert_stdout::assert_stdout, assert_success::assert_success, tempdir::tempdir, test::Test,

683
tests/json.rs Normal file
View File

@ -0,0 +1,683 @@
use crate::common::*;
fn test(justfile: &str, value: Value) {
Test::new()
.justfile(justfile)
.args(&["--dump", "--dump-format", "json", "--unstable"])
.stdout(format!("{}\n", serde_json::to_string(&value).unwrap()))
.run();
}
#[test]
fn alias() {
test(
"
alias f := foo
foo:
",
json!({
"first": "foo",
"aliases": {
"f": {
"name": "f",
"target": "foo",
}
},
"assignments": {},
"recipes": {
"foo": {
"body": [],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn assignment() {
test(
"foo := 'bar'",
json!({
"aliases": {},
"assignments": {
"foo": {
"export": false,
"name": "foo",
"value": "bar",
}
},
"first": null,
"recipes": {},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn body() {
test(
"
foo:
bar
abc{{ 'xyz' }}def
",
json!({
"aliases": {},
"assignments": {},
"first": "foo",
"recipes": {
"foo": {
"body": [
["bar"],
["abc", ["xyz"], "def"],
],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn dependencies() {
test(
"
foo:
bar: foo
",
json!({
"aliases": {},
"assignments": {},
"first": "foo",
"recipes": {
"bar": {
"doc": null,
"name": "bar",
"body": [],
"dependencies": [{
"arguments": [],
"recipe": "foo"
}],
"parameters": [],
"priors": 1,
"private": false,
"quiet": false,
"shebang": false,
},
"foo": {
"body": [],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn dependency_argument() {
test(
"
x := 'foo'
foo *args:
bar: (
foo
'baz'
('baz')
('a' + 'b')
`echo`
x
if 'a' == 'b' { 'c' } else { 'd' }
arch()
env_var('foo')
join('a', 'b')
replace('a', 'b', 'c')
)
",
json!({
"aliases": {},
"first": "foo",
"assignments": {
"x": {
"export": false,
"name": "x",
"value": "foo",
},
},
"recipes": {
"bar": {
"doc": null,
"name": "bar",
"body": [],
"dependencies": [{
"arguments": [
"baz",
"baz",
["concatinate", "a", "b"],
["evaluate", "echo"],
["variable", "x"],
["if", "==", "a", "b", "c", "d"],
["call", "arch"],
["call", "env_var", "foo"],
["call", "join", "a", "b"],
["call", "replace", "a", "b", "c"],
],
"recipe": "foo"
}],
"parameters": [],
"priors": 1,
"private": false,
"quiet": false,
"shebang": false,
},
"foo": {
"body": [],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [
{
"name": "args",
"export": false,
"default": null,
"kind": "star",
}
],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn doc_comment() {
test(
"# hello\nfoo:",
json!({
"aliases": {},
"first": "foo",
"assignments": {},
"recipes": {
"foo": {
"body": [],
"dependencies": [],
"doc": "hello",
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn empty_justfile() {
test(
"",
json!({
"aliases": {},
"assignments": {},
"first": null,
"recipes": {},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn parameters() {
test(
"
a:
b x:
c x='y':
d +x:
e *x:
f $x:
",
json!({
"aliases": {},
"first": "a",
"assignments": {},
"recipes": {
"a": {
"body": [],
"dependencies": [],
"doc": null,
"name": "a",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
},
"b": {
"body": [],
"dependencies": [],
"doc": null,
"name": "b",
"parameters": [
{
"name": "x",
"export": false,
"default": null,
"kind": "singular",
},
],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
},
"c": {
"body": [],
"dependencies": [],
"doc": null,
"name": "c",
"parameters": [
{
"name": "x",
"export": false,
"default": "y",
"kind": "singular",
}
],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
},
"d": {
"body": [],
"dependencies": [],
"doc": null,
"name": "d",
"parameters": [
{
"name": "x",
"export": false,
"default": null,
"kind": "plus",
}
],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
},
"e": {
"body": [],
"dependencies": [],
"doc": null,
"name": "e",
"parameters": [
{
"name": "x",
"export": false,
"default": null,
"kind": "star",
}
],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
},
"f": {
"body": [],
"dependencies": [],
"doc": null,
"name": "f",
"parameters": [
{
"name": "x",
"export": true,
"default": null,
"kind": "singular",
}
],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
},
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn priors() {
test(
"
a:
b: a && c
c:
",
json!({
"aliases": {},
"assignments": {},
"first": "a",
"recipes": {
"a": {
"body": [],
"dependencies": [],
"doc": null,
"name": "a",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
},
"b": {
"body": [],
"dependencies": [
{
"arguments": [],
"recipe": "a",
},
{
"arguments": [],
"recipe": "c",
}
],
"doc": null,
"name": "b",
"private": false,
"quiet": false,
"shebang": false,
"parameters": [],
"priors": 1,
},
"c": {
"body": [],
"dependencies": [],
"doc": null,
"name": "c",
"parameters": [],
"private": false,
"quiet": false,
"shebang": false,
"parameters": [],
"priors": 0,
},
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn private() {
test(
"_foo:",
json!({
"aliases": {},
"assignments": {},
"first": "_foo",
"recipes": {
"_foo": {
"body": [],
"dependencies": [],
"doc": null,
"name": "_foo",
"parameters": [],
"priors": 0,
"private": true,
"quiet": false,
"shebang": false,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn quiet() {
test(
"@foo:",
json!({
"aliases": {},
"assignments": {},
"first": "foo",
"recipes": {
"foo": {
"body": [],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": true,
"shebang": false,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn requires_unstable() {
Test::new()
.justfile("foo:")
.args(&["--dump", "--dump-format", "json"])
.stderr("error: The JSON dump format is currently unstable. Invoke `just` with the `--unstable` flag to enable unstable features.\n")
.status(EXIT_FAILURE)
.run();
}
#[test]
fn settings() {
test(
"
set dotenv-load
set export
set positional-arguments
set shell := ['a', 'b', 'c']
foo:
#!bar
",
json!({
"aliases": {},
"assignments": {},
"first": "foo",
"recipes": {
"foo": {
"body": [["#!bar"]],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": true,
}
},
"settings": {
"dotenv_load": true,
"export": true,
"positional_arguments": true,
"shell": {
"arguments": ["b", "c"],
"command": "a",
},
},
"warnings": [],
}),
);
}
#[test]
fn shebang() {
test(
"
foo:
#!bar
",
json!({
"aliases": {},
"assignments": {},
"first": "foo",
"recipes": {
"foo": {
"body": [["#!bar"]],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": true,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}
#[test]
fn simple() {
test(
"foo:",
json!({
"aliases": {},
"assignments": {},
"first": "foo",
"recipes": {
"foo": {
"body": [],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
}
},
"settings": {
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
},
"warnings": [],
}),
);
}

View File

@ -23,6 +23,7 @@ mod functions;
mod init; mod init;
mod interrupts; mod interrupts;
mod invocation_directory; mod invocation_directory;
mod json;
mod misc; mod misc;
mod positional_arguments; mod positional_arguments;
mod quiet; mod quiet;