Skip to content

Commit 13fab22

Browse files
authored
DSL refactor and Flags (options) parsing (#21)
* bump versions * wip - new DSL - accurate parser (combinators) - flags * remove dead code * refactor: simplified parser, need to fix tests * simplify * feat: add extensive cli spec definition validation * feat: implement dispatcher * fix: tests * make credo happy
1 parent 1e179bb commit 13fab22

26 files changed

+1527
-708
lines changed

.formatter.exs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1+
local_without_parens = [
2+
defcommand: 2,
3+
subcommand: 2,
4+
value: 2,
5+
flag: 2,
6+
short: 1,
7+
description: 1
8+
]
9+
110
[
11+
import_deps: [:nimble_parsec],
212
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
3-
export: [locals_without_parens: [defcommand: 2]],
4-
locals_without_parens: [defcommand: 2]
13+
export: [locals_without_parens: local_without_parens],
14+
locals_without_parens: local_without_parens
515
]

.github/pull_request_template.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Problem
2+
3+
<!-- what problem is the PR is trying to solve? -->
4+
5+
## Solution
6+
7+
<!-- how is the PR solving the problem? -->
8+
9+
## Rationale
10+
11+
<!-- why was it implemented the way it was? -->

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
lint:
13+
runs-on: ubuntu-latest
14+
15+
strategy:
16+
matrix:
17+
elixir: [1.17.0]
18+
otp: [27.0]
19+
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v3
23+
24+
- name: Set up Elixir
25+
uses: erlef/setup-beam@v1
26+
with:
27+
elixir-version: ${{ matrix.elixir }}
28+
otp-version: ${{ matrix.otp }}
29+
30+
- name: Install dependencies
31+
run: mix deps.get
32+
33+
- name: Clean build
34+
run: mix clean
35+
36+
- name: Check code formatting
37+
run: mix format --check-formatted
38+
39+
- name: Run Credo
40+
run: mix credo --strict
41+
42+
- name: Run tests
43+
run: mix test

.github/workflows/lint.yml

Lines changed: 0 additions & 24 deletions
This file was deleted.

.github/workflows/test.yml

Lines changed: 0 additions & 20 deletions
This file was deleted.

README.md

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,26 @@ An `Elixir` library to write command line apps in a cleaner and elegant way!
1717
[![Hex.pm](https://img.shields.io/hexpm/v/nexus_cli.svg)](https://hex.pm/packages/nexus_cli)
1818
[![Downloads](https://img.shields.io/hexpm/dt/nexus_cli.svg)](https://hex.pm/packages/nexus_cli)
1919
[![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/nexus_cli)
20-
[![lint](https://github.com/zoedsoupe/nexus/actions/workflows/lint.yml/badge.svg)](https://github.com/zoedsoupe/nexus/actions/workflows/lint.yml)
21-
[![test](https://github.com/zoedsoupe/nexus/actions/workflows/test.yml/badge.svg)](https://github.com/zoedsoupe/nexus/actions/workflows/test.yml)
20+
[![ci](https://github.com/zoedsoupe/nexus/actions/workflows/ci.yml/badge.svg)](https://github.com/zoedsoupe/nexus/actions/workflows/ci.yml)
2221

2322
## Example
2423

25-
```elixir dark
24+
```elixir
2625
defmodule MyCLI do
27-
use Nexus
26+
@moduledoc "This will be used into as help"
2827

29-
defcommand :ping, type: :null, doc: "Answers 'pong'"
30-
defcommand :fizzbuzz, type: :integer, required: true, doc: "Plays fizzbuzz"
31-
defcommand :mode, type: {:enum, ~w[fast slow]a}, required: true, doc: "Defines the command mode"
28+
use Nexus.CLI
3229

33-
@impl Nexus.CLI
34-
# no input as type == :null
35-
def handle_input(:ping), do: IO.puts("pong")
30+
defcommand :fizzbuzz do
31+
description "Plays fizzbuzz - this will also be used as help"
3632

37-
@impl Nexus.CLI
38-
# input can be named to anything
39-
@spec handle_input(atom, input) :: :ok
40-
when input: Nexus.Command.Input.t()
41-
def handle_input(:fizzbuzz, %{value: value}) do
42-
cond do
43-
rem(value, 3) == 0 -> IO.puts("fizz")
44-
rem(value, 5) == 0 -> IO.puts("buzz")
45-
rem(value, 3) == 0 and rem(value, 5) == 0 -> IO.puts("fizzbuzz")
46-
true -> IO.puts value
47-
end
33+
value :integer, required: true
4834
end
4935

50-
def handle_input(:mode, %{value: :fast), do: IO.puts "Hare"
51-
def handle_input(:mode, %{value: :slow), do: IO.puts "Tortoise"
36+
@impl Nexus.CLI
37+
def handle_input(:fizzbuzz, %{args: [input]}) do
38+
# plays fizzbuzz
39+
end
5240
end
5341
```
5442

examples/escript/example.ex

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,34 @@ defmodule Escript.Example do
1313
from the `main/1` escript funciton, as can seen below.
1414
"""
1515

16-
use Nexus
16+
use Nexus.CLI
1717

18-
defcommand :foo, required: true, type: :string, doc: "Command that receives a string as argument and prints it."
19-
defcommand :fizzbuzz, type: {:enum, ~w(fizz buzz)a}, doc: "Fizz bUZZ", required: true
18+
defcommand :foo do
19+
description "Command that receives a string as argument and prints it."
2020

21-
defcommand :foo_bar, type: :null, doc: "Teste" do
22-
defcommand :foo, default: "hello", doc: "Hello"
23-
defcommand :bar, default: "hello", doc: "Hello"
21+
value :string, required: true
22+
end
23+
24+
defcommand :fizzbuzz do
25+
description "Fizz bUZZ"
26+
27+
value {:enum, ~w(fizz buzz)a}, required: true
28+
end
29+
30+
defcommand :foo_bar do
31+
description "Teste"
32+
33+
subcommand :foo do
34+
description "hello"
35+
36+
value :string, required: false, default: "hello"
37+
end
38+
39+
subcommand :bar do
40+
description "hello"
41+
42+
value :string, required: false, default: "hello"
43+
end
2444
end
2545

2646
@impl true
@@ -49,8 +69,5 @@ defmodule Escript.Example do
4969
:ok
5070
end
5171

52-
Nexus.help()
53-
Nexus.parse()
54-
55-
defdelegate main(args), to: __MODULE__, as: :run
72+
# defdelegate main(args), to: __MODULE__, as: :run
5673
end

examples/file_management.ex

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
defmodule MyCLI do
2+
@moduledoc """
3+
MyCLI provides file operations such as copy, move, and delete using the Nexus.CLI DSL.
4+
"""
5+
6+
use Nexus.CLI, otp_app: :nexus_cli
7+
8+
defcommand :file do
9+
description "Performs file operations such as copy, move, and delete."
10+
11+
subcommand :copy do
12+
description "Copies files from source to destination."
13+
14+
value :string, required: true, as: :source
15+
value :string, required: true, as: :dest
16+
17+
flag :level do
18+
value :integer, required: false
19+
end
20+
21+
flag :verbose do
22+
short :v
23+
description "Enables verbose output."
24+
end
25+
26+
flag :recursive do
27+
short :rc
28+
description "Copies directories recursively."
29+
end
30+
end
31+
32+
subcommand :move do
33+
description "Moves files from source to destination."
34+
35+
value :string, required: true, as: :source
36+
value :string, required: true, as: :dest
37+
38+
flag :force do
39+
short :f
40+
description "Forces the move without confirmation."
41+
end
42+
43+
flag :verbose do
44+
short :v
45+
description "Enables verbose output."
46+
end
47+
end
48+
49+
subcommand :delete do
50+
description "Deletes specified files or directories."
51+
52+
value {:list, :string}, required: true, as: :targets
53+
54+
flag :force do
55+
short :f
56+
description "Forces deletion without confirmation."
57+
end
58+
59+
flag :recursive do
60+
short :rc
61+
description "Deletes directories recursively."
62+
end
63+
64+
flag :verbose do
65+
short :v
66+
description "Enables verbose output."
67+
end
68+
end
69+
end
70+
71+
@impl Nexus.CLI
72+
def handle_input([:file, :copy], %{args: args, flags: flags}) do
73+
if flags.verbose do
74+
IO.puts("Copying from #{args.source} to #{args.dest}")
75+
end
76+
77+
if flags.recursive do
78+
IO.puts("Recursive copy enabled")
79+
end
80+
81+
# Implement actual copy logic here
82+
IO.puts("Copied #{args.source} to #{args.dest}")
83+
:ok
84+
end
85+
86+
def handle_input([:file, :move], %{args: args, flags: flags}) do
87+
if flags.verbose do
88+
IO.puts("Moving from #{args.source} to #{args.dest}")
89+
end
90+
91+
if flags.force do
92+
IO.puts("Force move enabled")
93+
end
94+
95+
# Implement actual move logic here
96+
IO.puts("Moved #{args.source} to #{args.dest}")
97+
:ok
98+
end
99+
100+
def handle_input([:file, :delete], %{args: args, flags: flags}) do
101+
if flags.verbose do
102+
IO.puts("Deleting targets: #{Enum.join(args.targets, ", ")}")
103+
end
104+
105+
if flags.recursive do
106+
IO.puts("Recursive delete enabled")
107+
end
108+
109+
if flags.force do
110+
IO.puts("Force delete enabled")
111+
end
112+
113+
# Implement actual delete logic here
114+
Enum.each(args.targets, fn target ->
115+
IO.puts("Deleted #{target}")
116+
end)
117+
118+
:ok
119+
end
120+
121+
def handle_input(command, input) do
122+
IO.puts("Unknown command or invalid parameters")
123+
IO.inspect(command, label: "CMD")
124+
IO.inspect(input, label: "INPUT")
125+
:error
126+
end
127+
end

0 commit comments

Comments
 (0)