rubycli 0.1.2 → 0.1.5

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.
data/lib/rubycli/cli.rb CHANGED
@@ -37,7 +37,7 @@ module Rubycli
37
37
 
38
38
  if should_show_help?(args)
39
39
  @help_renderer.print_help(target, catalog)
40
- exit(0)
40
+ return 0
41
41
  end
42
42
 
43
43
  command = args.shift
@@ -48,6 +48,9 @@ module Rubycli
48
48
  else
49
49
  execute_method(entry.method, command, args, cli_mode)
50
50
  end
51
+ rescue Rubycli::ArgumentError => e
52
+ warn "[ERROR] #{e.message}"
53
+ 1
51
54
  end
52
55
 
53
56
  def available_commands(target)
@@ -105,7 +108,7 @@ module Rubycli
105
108
  error_msg = "Command '#{command}' is not available."
106
109
  puts error_msg
107
110
  @help_renderer.print_help(target, catalog)
108
- exit(1)
111
+ 1
109
112
  end
110
113
  end
111
114
 
@@ -113,9 +116,11 @@ module Rubycli
113
116
  method = target.method(:call)
114
117
  pos_args, kw_args = @argument_parser.parse(args, method)
115
118
  Rubycli.apply_argument_coercions(pos_args, kw_args)
119
+ @argument_parser.validate_inputs(method, pos_args, kw_args)
116
120
  begin
117
121
  result = Rubycli.call_target(target, pos_args, kw_args)
118
122
  @result_emitter.emit(result)
123
+ 0
119
124
  rescue StandardError => e
120
125
  handle_execution_error(e, command, method, pos_args, kw_args, cli_mode)
121
126
  end
@@ -132,15 +137,16 @@ module Rubycli
132
137
  def execute_parameterless_method(method_obj, command, args, cli_mode)
133
138
  if help_requested_for_parameterless?(args)
134
139
  puts usage_for_method(command, method_obj)
135
- exit(0)
140
+ return 0
136
141
  end
137
142
 
138
143
  begin
139
144
  result = method_obj.call
140
145
  debug_log "Parameterless method returned: #{result.inspect}"
141
146
  if result
142
- run(result, args, false)
147
+ return run(result, args, false)
143
148
  end
149
+ 0
144
150
  rescue StandardError => e
145
151
  handle_execution_error(e, command, method_obj, [], {}, cli_mode)
146
152
  end
@@ -149,15 +155,16 @@ module Rubycli
149
155
  def execute_method_with_params(method_obj, command, args, cli_mode)
150
156
  pos_args, kw_args = @argument_parser.parse(args, method_obj)
151
157
  Rubycli.apply_argument_coercions(pos_args, kw_args)
152
-
153
158
  if should_show_method_help?(pos_args, kw_args)
154
159
  puts usage_for_method(command, method_obj)
155
- exit(0)
160
+ return 0
156
161
  end
162
+ @argument_parser.validate_inputs(method_obj, pos_args, kw_args)
157
163
 
158
164
  begin
159
165
  result = Rubycli.call_target(method_obj, pos_args, kw_args)
160
166
  @result_emitter.emit(result)
167
+ 0
161
168
  rescue StandardError => e
162
169
  handle_execution_error(e, command, method_obj, pos_args, kw_args, cli_mode)
163
170
  end
@@ -181,7 +188,7 @@ module Rubycli
181
188
  if cli_mode && !arguments_match?(method_obj, pos_args, kw_args) && usage_error?(error)
182
189
  puts "Error: #{error.message}"
183
190
  puts usage_for_method(command, method_obj)
184
- exit(1)
191
+ 1
185
192
  else
186
193
  raise error
187
194
  end
@@ -3,7 +3,7 @@
3
3
  module Rubycli
4
4
  module CommandLine
5
5
  USAGE = <<~USAGE
6
- Usage: rubycli [--new|-n] [--pre-script=<src>] [--json-args|-j | --eval-args|-e] <target-path> [<class-or-module>] [-- <cli-args>...]
6
+ Usage: rubycli [--new|-n] [--pre-script=<src>] [--json-args|-j | --eval-args|-e | --eval-lax|-E] [--strict] [--check|-c] <target-path> [<class-or-module>] [-- <cli-args>...]
7
7
 
8
8
  Examples:
9
9
  rubycli scripts/sample_runner.rb echo --message hello
@@ -15,7 +15,11 @@ module Rubycli
15
15
  --pre-script=<src> Evaluate Ruby code and use its result as the exposed target (--init alias; also accepts space-separated form)
16
16
  --json-args, -j Parse all following arguments strictly as JSON (no YAML literals)
17
17
  --eval-args, -e Evaluate following arguments as Ruby code
18
- (Note: --json-args and --eval-args are mutually exclusive)
18
+ --eval-lax, -E Evaluate as Ruby but fall back to raw strings when parsing fails
19
+ --auto-target, -a Auto-select the only callable constant when names don't match
20
+ --strict Enforce documented input types/choices (invalid values abort)
21
+ --check, -c Validate documentation/comments without executing commands
22
+ (Note: --json-args cannot be combined with --eval-args or --eval-lax)
19
23
  (Note: Every option that accepts a value understands both --flag=value and --flag value forms.)
20
24
 
21
25
  When <class-or-module> is omitted, Rubycli infers it from the file name in CamelCase.
@@ -38,6 +42,9 @@ module Rubycli
38
42
  new_flag = false
39
43
  json_mode = false
40
44
  eval_mode = false
45
+ eval_lax_mode = false
46
+ constant_mode = nil
47
+ check_mode = false
41
48
  pre_script_sources = []
42
49
 
43
50
  loop do
@@ -60,7 +67,7 @@ module Rubycli
60
67
  flag = args.shift
61
68
  src = args.shift
62
69
  unless src
63
- warn "#{flag} requires a file path or inline Ruby code"
70
+ warn "[ERROR] #{flag} requires a file path or inline Ruby code"
64
71
  return 1
65
72
  end
66
73
  context = File.file?(src) ? File.expand_path(src) : "(inline #{flag})"
@@ -71,8 +78,26 @@ module Rubycli
71
78
  when '--eval-args', '-e'
72
79
  eval_mode = true
73
80
  args.shift
81
+ when '--eval-lax', '-E'
82
+ eval_mode = true
83
+ eval_lax_mode = true
84
+ args.shift
85
+ when '--strict'
86
+ Rubycli.environment.enable_strict_input!
87
+ args.shift
88
+ when '--check', '-c'
89
+ check_mode = true
90
+ Rubycli.environment.enable_doc_check!
91
+ args.shift
74
92
  when '--print-result'
75
93
  args.shift
94
+ when '--debug'
95
+ args.shift
96
+ warn "[ERROR] --debug flag has been removed; set RUBYCLI_DEBUG=true instead."
97
+ return 1
98
+ when '--auto-target', '-a'
99
+ constant_mode = :auto
100
+ args.shift
76
101
  when '--'
77
102
  args.shift
78
103
  break
@@ -95,10 +120,32 @@ module Rubycli
95
120
  args.shift if args.first == '--'
96
121
 
97
122
  if json_mode && eval_mode
98
- warn '--json-args and --eval-args cannot be used at the same time'
123
+ warn '[ERROR] --json-args cannot be combined with --eval-args or --eval-lax'
124
+ return 1
125
+ end
126
+
127
+ if check_mode && (json_mode || eval_mode)
128
+ warn '[ERROR] --check cannot be combined with --json-args or --eval-args'
129
+ return 1
130
+ end
131
+
132
+ if check_mode && !args.empty?
133
+ warn '[ERROR] --check does not accept command arguments'
99
134
  return 1
100
135
  end
101
136
 
137
+ Rubycli.environment.clear_documentation_issues!
138
+
139
+ if check_mode
140
+ return Rubycli::Runner.check(
141
+ target_path,
142
+ class_or_module,
143
+ new: new_flag,
144
+ pre_scripts: pre_script_sources,
145
+ constant_mode: constant_mode
146
+ )
147
+ end
148
+
102
149
  Rubycli::Runner.execute(
103
150
  target_path,
104
151
  class_or_module,
@@ -106,12 +153,17 @@ module Rubycli
106
153
  new: new_flag,
107
154
  json: json_mode,
108
155
  eval_args: eval_mode,
109
- pre_scripts: pre_script_sources
156
+ eval_lax: eval_lax_mode,
157
+ pre_scripts: pre_script_sources,
158
+ constant_mode: constant_mode
110
159
  )
111
160
 
112
161
  0
113
162
  rescue Rubycli::Runner::PreScriptError => e
114
- warn e.message
163
+ warn "[ERROR] #{e.message}"
164
+ 1
165
+ rescue Rubycli::Runner::Error => e
166
+ warn "[ERROR] #{e.message}"
115
167
  1
116
168
  end
117
169
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubycli
4
+ # Observes constants defined while loading a file.
5
+ class ConstantCapture
6
+ def initialize
7
+ @captured = Hash.new { |hash, key| hash[key] = [] }
8
+ end
9
+
10
+ def capture(file)
11
+ trace = TracePoint.new(:class) do |tp|
12
+ location = tp.path
13
+ next unless location && same_file?(file, location)
14
+
15
+ constant_name = qualified_name_for(tp.self)
16
+ next unless constant_name
17
+
18
+ @captured[file] << constant_name
19
+ end
20
+
21
+ trace.enable
22
+ yield
23
+ ensure
24
+ trace&.disable
25
+ end
26
+
27
+ def constants_for(file)
28
+ Array(@captured[normalize(file)]).uniq
29
+ end
30
+
31
+ private
32
+
33
+ def same_file?(target, candidate)
34
+ normalize(target) == normalize(candidate)
35
+ end
36
+
37
+ def normalize(file)
38
+ File.expand_path(file.to_s)
39
+ end
40
+
41
+ def qualified_name_for(target)
42
+ return nil unless target.respond_to?(:name)
43
+
44
+ name = target.name
45
+ return nil unless name && !name.empty? && !name.start_with?('#<')
46
+
47
+ name
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubycli
4
+ module Documentation
5
+ # Extracts contiguous comment blocks that appear immediately before a method.
6
+ class CommentExtractor
7
+ def initialize
8
+ @file_cache = {}
9
+ end
10
+
11
+ def extract(file, line_number)
12
+ return [] unless file && line_number
13
+
14
+ lines = cached_lines_for(file)
15
+ index = line_number - 2
16
+ block = []
17
+
18
+ while index >= 0
19
+ line = lines[index]
20
+ break unless comment_line?(line)
21
+
22
+ block << line
23
+ index -= 1
24
+ end
25
+
26
+ block.reverse.map { |line| strip_comment_prefix(line) }
27
+ rescue Errno::ENOENT
28
+ []
29
+ end
30
+
31
+ def reset!
32
+ @file_cache.clear
33
+ end
34
+
35
+ private
36
+
37
+ def cached_lines_for(file)
38
+ @file_cache[file] ||= File.readlines(file, chomp: true)
39
+ end
40
+
41
+ def comment_line?(line)
42
+ return false unless line
43
+
44
+ line.lstrip.start_with?('#')
45
+ end
46
+
47
+ def strip_comment_prefix(line)
48
+ line.lstrip.sub(/^#/, '').lstrip
49
+ end
50
+ end
51
+ end
52
+ end