clino 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 371a8130998852d6dd08f5abb9d0a64d1c984fd2c0224ab3b9313b88e9e11d27
4
- data.tar.gz: 4fbff42de01c1bb8c746882a9670f4928de0c09c7e7d7ba11fb6e0d4e85cc9bc
3
+ metadata.gz: df8da7e0b98d842743ff6574a23b88f48382faa9ccc218734b9696db55758799
4
+ data.tar.gz: ec26f662643aa566eaf2572d79a0d50dbb96a1d23550899329967a3299708b1f
5
5
  SHA512:
6
- metadata.gz: 1a7772c11b06cea8b0c1b71aff7f9ed6afd46d9229dbe0b88c328b2c3df214b70a44268c797bee6f7e43a5a12500de8224c91aa0d036fdcf9b95a5372390e75f
7
- data.tar.gz: 060acd626f965c30b4ba9af1ee1241e881c44b9c6af692d1ce70cb272d644bd9be0bd54dd67732aded98602c526ce4b31916dbebcddf4b7b9251858dffadb383
6
+ metadata.gz: 1accf77eb7242b41b00a9b19cedecb00a6116d429016123714a9bc0f08dc82d371216b9bfbe733ee411a18da97074c0e919f1e5b88248f5786293003be8ec695
7
+ data.tar.gz: b96437b5d23babb6a6749e23414ed5aef3bc3effe2690399ccb22136a5e686f3be8ec9f97a8bdd419ef66e69c993495bf4baeb25aac121c40d9f678cb094f9eb
data/CODE_OF_CONDUCT.md CHANGED
@@ -39,7 +39,7 @@ This Code of Conduct applies within all community spaces, and also applies when
39
39
 
40
40
  ## Enforcement
41
41
 
42
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at tikhon.zaikin@ematiq.com. All complaints will be reviewed and investigated promptly and fairly.
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at snusmumrmail@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
43
43
 
44
44
  All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
45
 
data/README.md CHANGED
@@ -6,12 +6,20 @@ It is inspired by the [Thor](https://github.com/rails/thor) and [Typer](https://
6
6
 
7
7
  ## Installation
8
8
 
9
- Add this line to your application's Gemfile:
9
+ Install this gem as a regular gem:
10
+
11
+ ```bash
12
+ gem install clino
13
+ ```
14
+
15
+ or add this line to your application's Gemfile:
10
16
 
11
17
  ```ruby
12
18
  gem 'clino'
13
19
  ```
14
20
 
21
+ Link: [RubyGems](https://rubygems.org/gems/clino)
22
+
15
23
  ## Usage
16
24
 
17
25
  ### Interfaces
@@ -40,14 +48,10 @@ Let's write a simple script that takes a name as an argument and prints a greeti
40
48
 
41
49
  require "clino/interfaces/min"
42
50
 
43
- def hello(name)
44
- "Hello, #{name}!"
45
- end
46
-
47
- Min.new(:hello).start
51
+ Min.new(->(name) { "Hello, #{name}!" }).start
48
52
  ```
49
53
 
50
- Writing method containing only business logic is enough, as the input and output handling is done by the `Min` interface.
54
+ Writing lambda containing only business logic is enough, as the input and output handling is done by the `Min` interface.
51
55
 
52
56
  Run the script with the following command:
53
57
 
@@ -64,7 +68,7 @@ ruby hello.rb --help
64
68
  or
65
69
 
66
70
  ```bash
67
- ruby hello.rb --h
71
+ ruby hello.rb -h
68
72
  ```
69
73
 
70
74
  It will print the following output:
@@ -77,7 +81,6 @@ Arguments:
77
81
  <name> [string]
78
82
 
79
83
  Usage: hello.rb [arguments] [options]
80
- Use --h, --help to print this help message.
81
84
  ```
82
85
  #### Randomizer Minimalistic Example
83
86
 
@@ -132,7 +135,7 @@ ruby min_randomizer.rb --help
132
135
  or
133
136
 
134
137
  ```bash
135
- ruby min_randomizer.rb --h
138
+ ruby min_randomizer.rb -h
136
139
  ```
137
140
 
138
141
  It will print the following output:
@@ -155,7 +158,6 @@ Options:
155
158
  [--incl] [string] [default: unknown]
156
159
 
157
160
  Usage: min_randomizer.rb [arguments] [options]
158
- Use --h, --help to print this help message.
159
161
  ```
160
162
 
161
163
  As we can see, ruby's metaprogramming capabilities allow us to create a simple CLI, however it doesn't provide a way to handle complex input.
@@ -164,7 +166,9 @@ Unfortunately, ruby doesn't allow inspecting the method's signature default valu
164
166
 
165
167
  #### Randomizer Advanced Example
166
168
 
167
- Let's write an advanced version of the randomizer that has CLI signature.
169
+ Let's write an advanced version of the randomizer that has CLI signature.
170
+
171
+ Our entry point is the `run` method, which is called with the parsed and validated input.
168
172
 
169
173
  ```ruby
170
174
  # randomizer.rb
@@ -210,7 +214,7 @@ RandomGeneratorCLI.new.start
210
214
  Run the script with the following command:
211
215
 
212
216
  ```bash
213
- ruby randomizer.rb 1 2 --alg uni --i y # => some number from [1.0, 2.0]
217
+ ruby randomizer.rb 1 2 --alg uni --i # => some number from [1.0, 2.0]
214
218
  ```
215
219
 
216
220
  Get help with the following command:
@@ -222,7 +226,7 @@ ruby randomizer.rb --help
222
226
  or
223
227
 
224
228
  ```bash
225
- ruby randomizer.rb --h
229
+ ruby randomizer.rb -h
226
230
  ```
227
231
 
228
232
  It will print the following output:
@@ -247,10 +251,9 @@ Options:
247
251
  # Algorithm to use (uni or exp)
248
252
  --alg (-a) [string]
249
253
  # Include upper bound
250
- [--[no-]incl] (-[no-]i) [bool] [default: false]
254
+ [--incl] (-i) [bool] [default: false]
251
255
 
252
256
  Usage: randomizer.rb [arguments] [options]
253
- Use --h, --help to print this help message.
254
257
  ```
255
258
 
256
259
  As we can see, all the default values, helping notes, and types are written out, and the input validation is handled automatically.
@@ -304,7 +307,6 @@ Arguments:
304
307
  <height> [positive_integer]
305
308
 
306
309
  Usage: weight_calculator.rb [arguments] [options]
307
- Use --h, --help to print this help message.
308
310
  ```
309
311
 
310
312
  ## Improvements
@@ -316,7 +318,6 @@ Use --h, --help to print this help message.
316
318
  - [ ] Add CI/CD
317
319
  - [ ] Add more examples
318
320
  - [ ] Create a proper documentation
319
- - [ ] Better boolean flag handling
320
321
 
321
322
  ## Contributing
322
323
 
@@ -6,3 +6,8 @@ def general_strip(str, substring = " ")
6
6
  str = str[0...-substring_len] while str.end_with?(substring)
7
7
  str
8
8
  end
9
+
10
+ def is_callable_with_parameters?(arg)
11
+ # Check if the argument is callable
12
+ arg.respond_to?(:call) && arg.parameters
13
+ end
@@ -4,10 +4,18 @@ require "optparse"
4
4
 
5
5
  module InputParser
6
6
  def parse_input(args_and_opts, signature)
7
+ args_and_opts = args_and_opts.map do |arg|
8
+ idx = 0
9
+ idx += 1 while arg[idx..].start_with?("-") && idx < arg.length
10
+ part_to_replace = arg[idx..].gsub("-", "_")
11
+ arg[0...idx] + part_to_replace
12
+ end
13
+
7
14
  input = {}
8
15
  pos_args = []
16
+ allowed_options = signature.allowed_options
9
17
  args_and_opts.each do |arg|
10
- break if arg.start_with?("--")
18
+ break if allowed_options.include?(arg)
11
19
 
12
20
  pos_args << arg
13
21
  end
@@ -19,32 +27,20 @@ module InputParser
19
27
  input[arg.name] = load_input arg.type, val if arg
20
28
  end
21
29
 
22
- OptionParser.new do |opt|
23
- signature.opts.each_key do |name|
24
- signature_opt = signature.opts[name]
25
- type = signature_opt.type
26
- aliases = signature_opt.aliases
27
- if type == :bool
28
- opt.on(*aliases, "--[no-]#{name}") do |v|
29
- input[name] = v
30
- end
31
- next
32
- end
33
-
34
- if signature_opt.required?
35
- opt.on(*aliases, "--#{name} #{name.upcase}") do |v|
36
- input[name] = load_input signature.opts[name].type, v
37
- end
38
- else
39
- opt.on(*aliases, "--#{name}") do |v|
40
- input[name] = load_input signature.opts[name].type, v
41
- end
42
- end
43
- end
44
- opt.on("--h", "--help", "Prints this help") do
45
- input[:help] = true
30
+ prev_opt_name = nil
31
+ allowed_option_to_name = signature.allowed_option_to_name
32
+ opts.each do |opt|
33
+ if allowed_options.include?(opt)
34
+ prev_opt_name = allowed_option_to_name[opt]
35
+ input[prev_opt_name] = true
36
+ elsif input[prev_opt_name] == true
37
+ input[prev_opt_name] = load_input signature.opts[prev_opt_name].type, opt
38
+ prev_opt_name = nil
39
+ else
40
+ raise ArgumentError, "Can't parse #{opt} option"
46
41
  end
47
- end.parse!(opts)
42
+ end
43
+
48
44
  input
49
45
  end
50
46
  end
@@ -16,8 +16,10 @@ module ResultObtainer
16
16
  end
17
17
 
18
18
  signature.opts.each do |opt_name, opt|
19
+ next if opt.exec_skip
20
+
19
21
  if opt.required?
20
- keyword_values[opt_name] = input[opt_name]
22
+ keyword_values[opt_name] = input[opt_name] if input.key?(opt_name)
21
23
  else
22
24
  keyword_values[opt_name] = input[opt_name] unless input[opt_name].nil?
23
25
  end
@@ -25,6 +27,8 @@ module ResultObtainer
25
27
 
26
28
  default_opts = signature.default_opts
27
29
  default_opts.each do |opt_name, opt|
30
+ next if signature.opts[opt_name].exec_skip
31
+
28
32
  keyword_values[opt_name] = opt unless keyword_values.key?(opt_name) || opt == :unknown
29
33
  end
30
34
 
@@ -33,9 +33,9 @@ module Base
33
33
  end
34
34
  end
35
35
 
36
- OptSignature = Struct.new(*BaseSignatureStruct.members, :aliases) do
36
+ OptSignature = Struct.new(*BaseSignatureStruct.members, :aliases, :exec_skip) do
37
37
  include BaseSignature
38
- def initialize(name:, type: :string, default: :none, desc: nil, aliases: [])
38
+ def initialize(name:, type: :string, default: :none, desc: nil, aliases: [], exec_skip: false)
39
39
  super
40
40
  end
41
41
  end
@@ -10,7 +10,16 @@ class CliSignature
10
10
  def initialize
11
11
  @args = {}
12
12
  @args_arr = []
13
- @opts = {}
13
+ @opts = {
14
+ help: Base::OptSignature.new(
15
+ name: :help,
16
+ type: :bool,
17
+ desc: "Prints this help message",
18
+ aliases: %w[-h],
19
+ default: false,
20
+ exec_skip: true
21
+ )
22
+ }
14
23
  @description = nil
15
24
  @help = nil
16
25
  end
@@ -49,7 +58,6 @@ class CliSignature
49
58
  unless @opts.empty?
50
59
  @help += "\nOptions:\n"
51
60
  @opts.each do |name, option|
52
- name = "[no-]#{name}" if option.type == :bool
53
61
  opt_part = " #{option.required? ? "--#{name}" : "[--#{name}]"}"
54
62
  if option.aliases && !option.aliases.empty?
55
63
  aliases = option.aliases.join(", ")
@@ -64,8 +72,6 @@ class CliSignature
64
72
 
65
73
  @help += "\nUsage: #{program_name} [arguments] [options]"
66
74
 
67
- @help += "\nUse --h, --help to print this help message.\n"
68
-
69
75
  @help
70
76
  end
71
77
 
@@ -108,4 +114,26 @@ class CliSignature
108
114
  end
109
115
  args
110
116
  end
117
+
118
+ def allowed_options
119
+ if @allowed_options.nil?
120
+ @allowed_options = Set.new
121
+ @allowed_option_to_name = {}
122
+ @opts.each_value do |opt|
123
+ long_opt = "--#{opt.name}"
124
+ @allowed_options.add(long_opt)
125
+ @allowed_option_to_name[long_opt] = opt.name
126
+ opt.aliases.each do |alias_|
127
+ @allowed_options.add(alias_)
128
+ @allowed_option_to_name[alias_] = opt.name
129
+ end
130
+ end
131
+ end
132
+ @allowed_options
133
+ end
134
+
135
+ def allowed_option_to_name
136
+ allowed_options if @allowed_option_to_name.nil?
137
+ @allowed_option_to_name
138
+ end
111
139
  end
@@ -5,14 +5,28 @@ module BaseCli
5
5
  include ResultObtainer
6
6
 
7
7
  def start(args = ARGV)
8
- input = parse_input args, @signature
8
+ input = parse_args_by_signature args
9
+ return if input.nil?
9
10
 
11
+ print_result run_instance input
12
+ end
13
+
14
+ def start_raw(args)
15
+ input = parse_args_by_signature args
16
+ return if input.nil?
17
+
18
+ run_instance input
19
+ end
20
+
21
+ def parse_args_by_signature(args)
22
+ parse_input args, @signature
23
+ end
24
+
25
+ def run_instance(input)
10
26
  if input[:help]
11
- print_help
12
- return
27
+ return @signature.help
13
28
  end
14
-
15
- print_result call_method_with_args @signature, @method, input
29
+ call_method_with_args @signature, @method, input
16
30
  end
17
31
 
18
32
  def print_help
@@ -8,7 +8,14 @@ require_relative "base/base_cli"
8
8
 
9
9
  class Cli
10
10
  class << self
11
- attr_reader :opt_buffer, :arg_buffer, :description
11
+ attr_reader :description
12
+ def opt_buffer
13
+ @opt_buffer || {}
14
+ end
15
+
16
+ def arg_buffer
17
+ @arg_buffer || {}
18
+ end
12
19
 
13
20
  def desc(description)
14
21
  @description = description
@@ -16,8 +23,7 @@ class Cli
16
23
 
17
24
  def opt(name, type: :string, desc: nil, aliases: [], default: :none)
18
25
  @opt_buffer ||= {}
19
- bool_prefix = type == :bool ? "[no-]" : ""
20
- aliases = aliases.map { |a| "-#{bool_prefix}#{general_strip a, "-"}" }
26
+ aliases = aliases.map { |a| "-#{general_strip a, "-"}" }
21
27
  default = load_input type, default unless default == :none
22
28
  @opt_buffer[name] = Base::OptSignature.new(name: name, type: type, default: default, desc: desc,
23
29
  aliases: aliases)
@@ -39,7 +45,7 @@ class Cli
39
45
  end
40
46
 
41
47
  @signature.opts.each_key do |name|
42
- @signature.add_opt(self.class.opt_buffer[name])
48
+ @signature.add_opt(self.class.opt_buffer[name]) if self.class.opt_buffer.key? name
43
49
  end
44
50
 
45
51
  @signature.description = self.class.description
@@ -13,7 +13,7 @@ class Min
13
13
 
14
14
  def initialize(command)
15
15
  @method = method command if command.is_a? Symbol
16
- @method = command if command.is_a? Method
16
+ @method = command if is_callable_with_parameters? command
17
17
 
18
18
  @signature = CliSignature.from_func @method
19
19
  end
data/lib/clino/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clino
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clino
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tikhon Zaikin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-11 00:00:00.000000000 Z
11
+ date: 2024-02-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |-
14
14
  clino is a minimalistic CLI generator