Skip to content

Commit 904c8b2

Browse files
committed
feat: json output
1 parent cf76d1b commit 904c8b2

17 files changed

Lines changed: 332 additions & 79 deletions

File tree

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,47 @@ gw co -
7878
| `gw sync [--dry-run] [--force]` | `gw s` | Fetch + prune stale worktrees |
7979
| `gw list` | `gw ls` | List worktrees |
8080

81+
## Global options
82+
83+
| Flag | Description |
84+
|---|---|
85+
| `--format=json` | Output results as JSON to stderr |
86+
| `--help` | Show help |
87+
88+
### JSON output
89+
90+
When `--format=json` is passed, output is written to stderr as JSON:
91+
92+
```bash
93+
gw --format=json list
94+
```
95+
96+
Output format:
97+
98+
```json
99+
{
100+
"path": "/path/to/worktree",
101+
"data": { ... },
102+
"messages": [{"level": "info", "text": "..."}]
103+
}
104+
```
105+
106+
Errors:
107+
108+
```json
109+
{
110+
"error": "error message",
111+
"messages": [{"level": "error", "text": "error message"}]
112+
}
113+
```
114+
115+
Example - list worktrees as JSON:
116+
117+
```bash
118+
$ gw --format=json list
119+
{"data":{"worktrees":[{"dir":"main","branch":"main","current":true}]},"messages":[...]}
120+
```
121+
81122
## Command details
82123

83124
### `gw clone` / `gw cl`

lib/git_work/cli.ex

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ defmodule GitWork.CLI do
2020
}
2121

2222
def run(args) do
23+
{format, args} = extract_format(args)
24+
2325
case args do
2426
["--help"] ->
2527
print_help()
@@ -37,42 +39,63 @@ defmodule GitWork.CLI do
3739
if "--help" in rest or "-h" in rest do
3840
print_command_help(command)
3941
else
40-
dispatch(command, rest)
42+
dispatch(command, rest, format)
4143
end
4244

4345
[] ->
4446
print_help()
4547
end
4648
end
4749

48-
defp dispatch(command, args) do
50+
defp extract_format(args) do
51+
case Enum.split_with(args, &(&1 == "--format=json")) do
52+
{format_args, rest} when format_args != [] ->
53+
{:json, rest}
54+
55+
_ ->
56+
{:text, args}
57+
end
58+
end
59+
60+
defp dispatch(command, args, format) do
4961
case Map.get(@commands, command) do
5062
nil ->
51-
IO.write(:stderr, "git-work: unknown command '#{command}'\n\n")
63+
GitWork.Output.print_error("unknown command '#{command}'", format)
5264
print_help()
5365
System.halt(1)
5466

5567
module ->
5668
result =
5769
try do
58-
module.run(args)
70+
module.run(args, format)
5971
rescue
6072
e ->
6173
{:error, "unexpected error: #{Exception.message(e)}"}
6274
end
6375

64-
handle_result(result)
76+
handle_result(result, format)
6577
end
6678
end
6779

68-
defp handle_result({:ok, output}) when is_binary(output) and output != "" do
69-
IO.puts(output)
80+
defp handle_result({:ok, %GitWork.Output{} = output}, format) do
81+
GitWork.Output.print(output, format)
82+
end
83+
84+
defp handle_result({:ok, output}, format) when is_binary(output) and output != "" do
85+
case format do
86+
:json ->
87+
json = JSON.encode!(%{path: output, messages: []})
88+
IO.write(:stderr, json <> "\n")
89+
90+
:text ->
91+
IO.puts(output)
92+
end
7093
end
7194

72-
defp handle_result({:ok, _}), do: :ok
95+
defp handle_result({:ok, _}, _format), do: :ok
7396

74-
defp handle_result({:error, message}) do
75-
IO.write(:stderr, "git-work: #{message}\n")
97+
defp handle_result({:error, message}, format) do
98+
GitWork.Output.print_error(message, format)
7699
System.halt(1)
77100
end
78101

@@ -89,7 +112,7 @@ defmodule GitWork.CLI do
89112

90113
defp print_help do
91114
IO.write(:stderr, """
92-
usage: git-work <command> [<args>]
115+
usage: git-work [--format=json] <command> [<args>]
93116
94117
Commands:
95118
activate <shell> Print shell integration (bash, zsh, fish)
@@ -100,8 +123,9 @@ defmodule GitWork.CLI do
100123
sync (s) [--dry-run] Fetch and prune stale worktrees
101124
list (ls) List all worktrees
102125
103-
Options:
104-
--help Show this help
126+
Global options:
127+
--format=json Output results as JSON to stderr
128+
--help Show this help
105129
106130
Run 'git-work <command> --help' for more information on a specific command.
107131
""")

lib/git_work/commands/activate.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,19 @@ defmodule GitWork.Commands.Activate do
2424
"""
2525
end
2626

27-
def run([shell]) do
27+
def run([shell], _format) do
2828
case ShellHook.generate(shell) do
2929
{:error, _} = err -> err
3030
code when is_binary(code) -> {:ok, code}
3131
end
3232
end
3333

34-
def run([]) do
34+
def run([], _format) do
3535
{:error,
3636
"activate requires a shell argument (#{Enum.join(ShellHook.supported_shells(), ", ")})"}
3737
end
3838

39-
def run(_args) do
39+
def run(_args, _format) do
4040
{:error, "activate takes exactly one argument: the shell name"}
4141
end
4242
end

lib/git_work/commands/checkout.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ defmodule GitWork.Commands.Checkout do
3939
"""
4040
end
4141

42-
def run(args) do
42+
def run(args, _format) do
4343
case args do
4444
["-b", branch] ->
4545
with {:ok, root} <- Project.find_root() do

lib/git_work/commands/clone.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ defmodule GitWork.Commands.Clone do
2222
"""
2323
end
2424

25-
def run(args) do
25+
def run(args, _format) do
2626
case args do
2727
[url] ->
2828
dir = Project.dir_from_url(url)

lib/git_work/commands/init.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ defmodule GitWork.Commands.Init do
2525
"""
2626
end
2727

28-
def run(_args) do
28+
def run(_args, _format) do
2929
cwd = File.cwd!()
3030
do_init(cwd)
3131
end

lib/git_work/commands/list.ex

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ defmodule GitWork.Commands.List do
1919
"""
2020
end
2121

22-
def run(_args) do
22+
def run(_args, format) do
2323
with {:ok, root} <- Project.find_root() do
2424
bare_dir = Project.bare_path(root)
2525

@@ -30,16 +30,34 @@ defmodule GitWork.Commands.List do
3030
|> parse_porcelain(root)
3131
|> Enum.reject(fn e -> String.starts_with?(e.dir, ".") end)
3232

33-
formatted = format_table(entries, File.cwd!())
34-
IO.write(:stderr, formatted)
35-
{:ok, ""}
33+
output_result = output_for_list(entries, File.cwd!())
34+
35+
case format do
36+
:json -> {:ok, output_result}
37+
:text -> GitWork.Output.print(output_result, :text); {:ok, ""}
38+
end
3639

3740
{:error, msg} ->
3841
{:error, "worktree list failed: #{msg}"}
3942
end
4043
end
4144
end
4245

46+
defp output_for_list(entries, cwd) do
47+
%GitWork.Output{
48+
data: %{
49+
worktrees: Enum.map(entries, fn e ->
50+
%{
51+
dir: e.dir,
52+
branch: e.branch,
53+
current: e.path == cwd
54+
}
55+
end)
56+
},
57+
messages: [%{level: :info, text: format_table(entries, cwd)}]
58+
}
59+
end
60+
4361
@doc false
4462
def parse_porcelain(output, project_root) do
4563
output

lib/git_work/commands/rm.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ defmodule GitWork.Commands.Rm do
3131
"""
3232
end
3333

34-
def run(args) do
34+
def run(args, _format) do
3535
{opts, rest, _} = OptionParser.parse(args, strict: [force: :boolean, yes: :boolean])
3636
force? = Keyword.get(opts, :force, false)
3737
yes? = Keyword.get(opts, :yes, false)

lib/git_work/commands/sync.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ defmodule GitWork.Commands.Sync do
2525
"""
2626
end
2727

28-
def run(args) do
28+
def run(args, _format) do
2929
{opts, _, _} =
3030
OptionParser.parse(args,
3131
strict: [dry_run: :boolean, force: :boolean],

0 commit comments

Comments
 (0)