openc3 7.1.1 → 7.2.0

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.
@@ -16,40 +16,52 @@ module OpenC3
16
16
  GENERATORS = %w(plugin target microservice widget conversion processor limits_response tool tool_vue tool_angular tool_react tool_svelte command_validator)
17
17
  TEMPLATES_DIR = "#{File.dirname(__FILE__)}/../../../templates"
18
18
 
19
+ # Generators that derive language from the target's target.txt
20
+ TARGET_DERIVED_GENERATORS = %w(conversion processor limits_response command_validator).freeze
21
+ # Generators that are JavaScript-only and ignore language flags
22
+ JS_ONLY_GENERATORS = %w(widget tool tool_vue tool_angular tool_react tool_svelte).freeze
23
+ # Generators that require an explicit language (or env/plugin default)
24
+ LANGUAGE_REQUIRED_GENERATORS = %w(target microservice).freeze
25
+
19
26
  # Called by openc3cli with ARGV[1..-1]
20
27
  def self.generate(args)
21
28
  if args[0].nil? || args[0] == '--help' || args[0] == '-h'
22
- puts "Usage: cli generate GENERATOR [ARGS...] (--ruby or --python)"
29
+ puts "Usage: cli generate GENERATOR [ARGS...] [--ruby | --python]"
23
30
  puts ""
24
31
  puts "Generate COSMOS components from templates"
25
32
  puts ""
26
33
  puts "Available Generators:"
27
- puts " plugin Create a new COSMOS plugin"
28
- puts " target Create a new target within a plugin"
29
- puts " microservice Create a new microservice within a plugin"
30
- puts " widget Create a new custom widget"
31
- puts " conversion Create a new conversion class for a target"
32
- puts " processor Create a new processor for a target"
33
- puts " limits_response Create a new limits response for a target"
34
- puts " command_validator Create a new command validator for a target"
35
- puts " tool Create a new tool (Vue.js by default)"
36
- puts " tool_vue Create a new Vue.js tool"
37
- puts " tool_angular Create a new Angular tool"
38
- puts " tool_react Create a new React tool"
39
- puts " tool_svelte Create a new Svelte tool"
34
+ puts " plugin Create a new COSMOS plugin (--ruby/--python optional, sets plugin default)"
35
+ puts " target Create a new target within a plugin (--ruby/--python required or inherited)"
36
+ puts " microservice Create a new microservice within a plugin (--ruby/--python required or inherited)"
37
+ puts " widget Create a new custom widget (JavaScript only)"
38
+ puts " conversion Create a new conversion class for a target (language inherited from target)"
39
+ puts " processor Create a new processor for a target (language inherited from target)"
40
+ puts " limits_response Create a new limits response for a target (language inherited from target)"
41
+ puts " command_validator Create a new command validator for a target (language inherited from target)"
42
+ puts " tool Create a new tool, Vue.js by default (JavaScript only)"
43
+ puts " tool_vue Create a new Vue.js tool (JavaScript only)"
44
+ puts " tool_angular Create a new Angular tool (JavaScript only)"
45
+ puts " tool_react Create a new React tool (JavaScript only)"
46
+ puts " tool_svelte Create a new Svelte tool (JavaScript only)"
40
47
  puts ""
41
48
  puts "Run 'cli generate GENERATOR --help' for detailed help on each generator."
42
49
  puts ""
50
+ puts "Language Resolution (for target/microservice):"
51
+ puts " 1. --ruby or --python flag"
52
+ puts " 2. OPENC3_LANGUAGE environment variable"
53
+ puts " 3. '# LANGUAGE ruby|python' comment in plugin.txt (set by 'generate plugin --ruby/--python')"
54
+ puts ""
43
55
  puts "Options:"
44
- puts " --ruby Generate Ruby code (or set OPENC3_LANGUAGE=ruby)"
45
- puts " --python Generate Python code (or set OPENC3_LANGUAGE=python)"
56
+ puts " --ruby Generate Ruby code"
57
+ puts " --python Generate Python code"
46
58
  puts " -h, --help Show this help message"
47
59
  puts ""
48
60
  puts "Examples:"
49
- puts " cli generate plugin MyPlugin --ruby"
50
- puts " cli generate target EXAMPLE --python"
51
- puts " cli generate widget SuperdataWidget --ruby"
52
- puts " cli generate conversion EXAMPLE STATUS --ruby"
61
+ puts " cli generate plugin MyPlugin --ruby # Plugin defaults future generators to Ruby"
62
+ puts " cli generate target EXAMPLE --python # Or inherit from plugin's stored default"
63
+ puts " cli generate widget SuperdataWidget # No language flag needed"
64
+ puts " cli generate conversion EXAMPLE STATUS # Language read from targets/EXAMPLE/target.txt"
53
65
  puts ""
54
66
  puts "Documentation:"
55
67
  puts " https://docs.openc3.com/docs/development/developing"
@@ -65,34 +77,259 @@ module OpenC3
65
77
  send("generate_#{args[0].to_s.downcase.gsub('-', '_')}", args)
66
78
  end
67
79
 
80
+ # Strip --ruby/--python tokens from args and return the resolved language ('rb'/'py'/nil).
81
+ # Aborts if both flags are supplied. Aborts if a language flag appears before the NAME
82
+ # positional arg (i.e. stripping flags would leave the generator with no NAME).
83
+ def self.extract_language!(args)
84
+ generator = args[0]
85
+ lang_flags = []
86
+ flag_indices = []
87
+ args.each_with_index do |arg, idx|
88
+ next if idx == 0 # don't touch the generator name itself
89
+ if arg == '--ruby' || arg == '--python'
90
+ lang_flags << arg
91
+ flag_indices << idx
92
+ end
93
+ end
94
+
95
+ if lang_flags.size > 1 && lang_flags.uniq.size > 1
96
+ abort("Cannot specify both --ruby and --python.")
97
+ end
98
+
99
+ # If a language flag is present but no positional NAME follows the generator name,
100
+ # the user wrote something like `cli generate plugin --python`. Abort with guidance.
101
+ if args[1] == '--ruby' || args[1] == '--python'
102
+ abort("NAME must come before the language flag. Example: cli generate #{generator} MyName #{args[1]}")
103
+ end
104
+
105
+ # Remove flag tokens from args in-place so downstream argument-position checks work.
106
+ flag_indices.reverse_each { |i| args.delete_at(i) }
107
+
108
+ case lang_flags.first
109
+ when '--python' then 'py'
110
+ when '--ruby' then 'rb'
111
+ else nil
112
+ end
113
+ end
114
+
115
+ # Read the '# LANGUAGE ruby|python' comment from plugin.txt in the current directory.
116
+ # Returns 'rb', 'py', or nil if the file or comment is absent.
117
+ def self.read_plugin_language
118
+ return nil unless File.exist?('plugin.txt')
119
+ File.foreach('plugin.txt') do |line|
120
+ if line =~ /^\s*#\s*LANGUAGE\s+(ruby|python)\b/i
121
+ return $1.downcase == 'python' ? 'py' : 'rb'
122
+ end
123
+ end
124
+ nil
125
+ end
126
+
127
+ # Convert OPENC3_LANGUAGE env var to 'rb'/'py' or nil.
128
+ def self.env_language
129
+ case ENV['OPENC3_LANGUAGE']
130
+ when 'python' then 'py'
131
+ when 'ruby' then 'rb'
132
+ else nil
133
+ end
134
+ end
135
+
136
+ # Argument validation + language resolution dispatch.
137
+ # After this returns, args no longer contains --ruby/--python tokens and
138
+ # @@language is set (except for generators that resolve language later,
139
+ # like target-derived ones that read target.txt).
68
140
  def self.check_args(args)
141
+ generator = args[0]
142
+ explicit_lang = extract_language!(args)
143
+
69
144
  args.each do |arg|
70
- if arg =~ /\s/ and args[0].to_s.downcase[0..3] != 'tool'
71
- abort("#{args[0].to_s.downcase} arguments can not have spaces!")
145
+ if arg =~ /\s/ and generator.to_s.downcase[0..3] != 'tool'
146
+ abort("#{generator.to_s.downcase} arguments can not have spaces!")
72
147
  end
73
148
  end
74
149
  # All generators except 'plugin' must be within an existing plugin
75
- if args[0] != 'plugin' and Dir.glob("*.gemspec").empty?
76
- abort("No gemspec file detected. #{args[0].to_s.downcase} generator should be run within an existing plugin.")
150
+ if generator != 'plugin' and Dir.glob("*.gemspec").empty?
151
+ abort("No gemspec file detected. #{generator.to_s.downcase} generator should be run within an existing plugin.")
77
152
  end
78
153
 
79
- gen_lang = ENV['OPENC3_LANGUAGE']
80
- if (args[-1] == '--python' || args[-1] == '--ruby')
81
- gen_lang = args[-1][2, 6]
82
- end
83
- case gen_lang
84
- when 'python'
85
- @@language = 'py'
86
- when 'ruby'
154
+ if JS_ONLY_GENERATORS.include?(generator)
155
+ if explicit_lang
156
+ puts "Note: --ruby/--python is ignored for the #{generator} generator (JavaScript only)."
157
+ end
158
+ # JS generators don't write any .rb/.py files but process_template still
159
+ # filters by @@language; set to 'rb' as a harmless default.
160
+ @@language = 'rb'
161
+ elsif TARGET_DERIVED_GENERATORS.include?(generator)
162
+ if explicit_lang
163
+ puts "Note: --ruby/--python is ignored for the #{generator} generator (language is inherited from the target's target.txt)."
164
+ end
165
+ # @@language is set later by the generator after it locates target.txt.
166
+ # Default to 'rb' here so any pre-target validation doesn't blow up.
87
167
  @@language = 'rb'
88
- else
89
- abort("One of --python or --ruby is required unless OPENC3_LANGUAGE is set.")
168
+ elsif LANGUAGE_REQUIRED_GENERATORS.include?(generator)
169
+ gen_lang = explicit_lang || env_language || read_plugin_language
170
+ unless gen_lang
171
+ abort("Language required for #{generator} generator. Pass --ruby or --python, set OPENC3_LANGUAGE, or add '# LANGUAGE ruby|python' to plugin.txt (the plugin generator does this automatically when given --ruby/--python).")
172
+ end
173
+ @@language = gen_lang
174
+ else # plugin
175
+ @@language = explicit_lang || env_language
176
+ # nil is allowed for plugin; templates are language-agnostic at the plugin level.
177
+ end
178
+ end
179
+
180
+ # Shared prelude for generators that resolve language via the flag → env var
181
+ # → plugin.txt comment chain (target and microservice). Handles the --help
182
+ # branch and the "exactly one positional NAME arg" arity check. Returns
183
+ # normally only when args are valid and the caller should proceed.
184
+ def self.validate_language_inheriting_args!(generator, args, example:, docs:)
185
+ if args[1].nil? || args[1] == '--help' || args[1] == '-h'
186
+ print_help(
187
+ usage: "cli generate #{generator} NAME [--ruby | --python]",
188
+ description: "Generate a new #{generator} within an existing plugin",
189
+ arguments: [
190
+ "NAME Name of the #{generator} (required)",
191
+ ' Will be uppercased and underscores/hyphens converted to underscores',
192
+ ],
193
+ options: [
194
+ "--ruby Generate Ruby #{generator} (optional)",
195
+ "--python Generate Python #{generator} (optional)",
196
+ ],
197
+ language_defaults: [
198
+ '1. OPENC3_LANGUAGE environment variable',
199
+ "2. '# LANGUAGE ruby|python' comment in plugin.txt",
200
+ ],
201
+ example: example,
202
+ docs: docs,
203
+ exit_code: (args[1].nil? ? 1 : 0),
204
+ )
205
+ end
206
+ if args.length != 2
207
+ abort("Usage: cli generate #{generator} <NAME> [--ruby | --python]")
208
+ end
209
+ end
210
+
211
+ # Shared structure for every per-generator help block. Pass a hash with the
212
+ # generator's usage line, description, arg/option/example lines, etc.
213
+ # See generate_plugin / generate_target_artifact for examples.
214
+ def self.print_help(opts)
215
+ puts "Usage: #{opts[:usage]}"
216
+ puts ""
217
+ puts opts[:description]
218
+ puts ""
219
+ puts "Arguments:"
220
+ opts.fetch(:arguments, []).each { |line| puts " #{line}" }
221
+ puts ""
222
+ puts "Options:"
223
+ opts.fetch(:options, []).each { |line| puts " #{line}" }
224
+ puts " -h, --help Show this help message"
225
+ Array(opts[:notes]).each do |note|
226
+ puts ""
227
+ puts note
228
+ end
229
+ if opts[:language_defaults]
230
+ puts ""
231
+ puts "Language Defaults (used when --ruby/--python is not given):"
232
+ opts[:language_defaults].each { |line| puts " #{line}" }
233
+ end
234
+ if opts[:extra_section]
235
+ puts ""
236
+ puts "#{opts[:extra_section][:title]}:"
237
+ opts[:extra_section][:lines].each { |line| puts " #{line}" }
238
+ end
239
+ puts ""
240
+ puts "Example:"
241
+ Array(opts[:example]).each { |line| puts line.empty? ? "" : " #{line}" }
242
+ unless opts[:is_plugin]
243
+ puts ""
244
+ puts "Note: Must be run from within an existing plugin directory"
245
+ Array(opts[:in_plugin_extra]).each { |line| puts " #{line}" }
246
+ end
247
+ puts ""
248
+ puts "Documentation:"
249
+ puts " #{opts[:docs]}"
250
+ exit(opts[:exit_code])
251
+ end
252
+
253
+ # Single implementation backing the four target-derived generators.
254
+ def self.generate_target_artifact(args, config)
255
+ generator = args[0]
256
+ if args[1].nil? || args[2].nil? || args[1] == '--help' || args[1] == '-h'
257
+ print_help(
258
+ usage: "cli generate #{generator} TARGET NAME",
259
+ description: "Generate a new #{config[:kind].downcase} for an existing target",
260
+ arguments: [
261
+ 'TARGET Target name (required, must exist)',
262
+ "NAME #{config[:kind]} name (required)",
263
+ " Will be uppercased with '_#{config[:suffix]}' suffix",
264
+ ],
265
+ notes: [
266
+ "Note: Language is inherited from the target's target.txt (LANGUAGE keyword).",
267
+ ' --ruby/--python flags are ignored.',
268
+ ],
269
+ example: [
270
+ "cli generate #{generator} EXAMPLE #{config[:help_example]}",
271
+ "Creates: targets/EXAMPLE/lib/#{config[:help_example].downcase}_#{generator}.rb (or .py)",
272
+ ],
273
+ docs: config[:docs],
274
+ exit_code: (args[1].nil? || args[2].nil? ? 1 : 0),
275
+ )
276
+ end
277
+ if args.length != 3
278
+ abort("Usage: cli generate #{generator} <TARGET> <NAME>")
279
+ end
280
+
281
+ target_name = args[1].upcase
282
+ target_path = "targets/#{target_name}"
283
+ unless File.exist?(target_path)
284
+ abort("Target '#{target_name}' does not exist! #{config[:kind_plural]} must be created for existing targets.")
285
+ end
286
+ @@language = read_target_language(target_path)
287
+
288
+ artifact_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_#{config[:suffix]}"
289
+ basename = "#{artifact_name.downcase}.#{@@language}"
290
+ filename = "#{target_path}/lib/#{basename}"
291
+
292
+ if File.exist?(filename)
293
+ abort("#{config[:kind]} #{filename} already exists!")
294
+ end
295
+
296
+ # Bind the class name to the template-specific local-variable name
297
+ # (e.g. conversion_class, processor_class) so the ERB templates resolve it.
298
+ template_binding = binding
299
+ template_binding.local_variable_set(config[:class_var].to_sym, basename.filename_to_class_name)
300
+
301
+ template_source = "#{config[:template_source]}.#{@@language}"
302
+ process_template("#{TEMPLATES_DIR}/#{config[:template]}", template_binding) do |fname|
303
+ fname.sub!(template_source, filename)
304
+ false
305
+ end
306
+
307
+ puts "#{config[:kind]} #{filename} successfully generated!"
308
+ puts config[:usage_intro]
309
+ directive = config[:usage_directive] % { basename: basename, name_upcase: args[2].upcase }
310
+ puts " #{directive}"
311
+ artifact_name
312
+ end
313
+
314
+ # Read 'LANGUAGE ruby|python' from a target's target.txt. Aborts if missing.
315
+ def self.read_target_language(target_path)
316
+ target_txt = "#{target_path}/target.txt"
317
+ unless File.exist?(target_txt)
318
+ abort("Could not find #{target_txt} to determine target language.")
90
319
  end
320
+ File.foreach(target_txt) do |line|
321
+ if line =~ /^\s*LANGUAGE\s+(ruby|python)\b/i
322
+ return $1.downcase == 'python' ? 'py' : 'rb'
323
+ end
324
+ end
325
+ abort("No LANGUAGE keyword found in #{target_txt}. Add 'LANGUAGE ruby' or 'LANGUAGE python' to determine the language for generated files.")
91
326
  end
92
327
 
93
328
  def self.process_template(template_dir, the_binding)
94
329
  Dir.glob("#{template_dir}/**/*", File::FNM_DOTMATCH).each do |file|
95
330
  next if File.basename(file) == '.'
331
+ # When @@language is nil (plugin generation with no language specified),
332
+ # don't filter — let all template files through.
96
333
  if @@language == 'rb' and File.extname(file) == '.py'
97
334
  # Ignore python files if we're ruby
98
335
  next
@@ -115,30 +352,35 @@ module OpenC3
115
352
 
116
353
  def self.generate_plugin(args)
117
354
  if args[1].nil? || args[1] == '--help' || args[1] == '-h'
118
- puts "Usage: cli generate plugin NAME (--ruby or --python)"
119
- puts ""
120
- puts "Generate a new COSMOS plugin"
121
- puts ""
122
- puts "Arguments:"
123
- puts " NAME Name of the plugin (required)"
124
- puts " Will be prefixed with 'openc3-cosmos-'"
125
- puts " Spaces, underscores, and hyphens will be converted to hyphens"
126
- puts ""
127
- puts "Options:"
128
- puts " --ruby Generate Ruby plugin (or set OPENC3_LANGUAGE=ruby)"
129
- puts " --python Generate Python plugin (or set OPENC3_LANGUAGE=python)"
130
- puts " -h, --help Show this help message"
131
- puts ""
132
- puts "Example:"
133
- puts " cli generate plugin demo --ruby"
134
- puts " Creates: openc3-cosmos-demo/"
135
- puts ""
136
- puts "Documentation:"
137
- puts " https://docs.openc3.com/docs/configuration/plugins"
138
- exit(args[1].nil? ? 1 : 0)
139
- end
140
- if args.length < 2 or args.length > 3
141
- abort("Usage: cli generate #{args[0]} <NAME> (--ruby or --python)")
355
+ print_help(
356
+ usage: 'cli generate plugin NAME [--ruby | --python]',
357
+ description: 'Generate a new COSMOS plugin',
358
+ arguments: [
359
+ 'NAME Name of the plugin (required)',
360
+ " Will be prefixed with 'openc3-cosmos-'",
361
+ ' Spaces, underscores, and hyphens will be converted to hyphens',
362
+ ],
363
+ options: [
364
+ "--ruby Set the plugin's default language to Ruby. Subsequent",
365
+ " 'cli generate target/microservice' invocations inside this",
366
+ ' plugin will default to Ruby. Recorded as a',
367
+ " '# LANGUAGE ruby' comment in plugin.txt.",
368
+ '--python Same as --ruby, but sets the default to Python.',
369
+ ],
370
+ example: [
371
+ 'cli generate plugin demo --ruby',
372
+ "Creates: openc3-cosmos-demo/ with '# LANGUAGE ruby' in plugin.txt",
373
+ '',
374
+ 'cli generate plugin demo',
375
+ 'Creates: openc3-cosmos-demo/ with no language default',
376
+ ],
377
+ is_plugin: true,
378
+ docs: 'https://docs.openc3.com/docs/configuration/plugins',
379
+ exit_code: (args[1].nil? ? 1 : 0),
380
+ )
381
+ end
382
+ if args.length != 2
383
+ abort("Usage: cli generate #{args[0]} <NAME> [--ruby | --python]")
142
384
  end
143
385
 
144
386
  # Create the local variables that are used in process_template below (see openc3/templates/plugin/plugin.gemspec as an example)
@@ -156,38 +398,25 @@ module OpenC3
156
398
  false
157
399
  end
158
400
 
401
+ # If a language was specified, persist it in plugin.txt so future target /
402
+ # microservice generators can default to it.
403
+ if @@language
404
+ lang_word = (@@language == 'py') ? 'python' : 'ruby'
405
+ existing = File.exist?('plugin.txt') ? File.read('plugin.txt') : ''
406
+ File.open('plugin.txt', 'w') do |file|
407
+ file.puts "# LANGUAGE #{lang_word}"
408
+ file.write(existing)
409
+ end
410
+ end
411
+
159
412
  puts "Plugin #{plugin_name} successfully generated!"
160
413
  return plugin_name
161
414
  end
162
415
 
163
416
  def self.generate_target(args)
164
- if args[1].nil? || args[1] == '--help' || args[1] == '-h'
165
- puts "Usage: cli generate target NAME (--ruby or --python)"
166
- puts ""
167
- puts "Generate a new target within an existing plugin"
168
- puts ""
169
- puts "Arguments:"
170
- puts " NAME Name of the target (required)"
171
- puts " Will be uppercased and underscores/hyphens converted to underscores"
172
- puts ""
173
- puts "Options:"
174
- puts " --ruby Generate Ruby target (or set OPENC3_LANGUAGE=ruby)"
175
- puts " --python Generate Python target (or set OPENC3_LANGUAGE=python)"
176
- puts " -h, --help Show this help message"
177
- puts ""
178
- puts "Example:"
179
- puts " cli generate target EXAMPLE --ruby"
180
- puts " Creates: targets/EXAMPLE/"
181
- puts ""
182
- puts "Note: Must be run from within an existing plugin directory"
183
- puts ""
184
- puts "Documentation:"
185
- puts " https://docs.openc3.com/docs/configuration/target"
186
- exit(args[1].nil? ? 1 : 0)
187
- end
188
- if args.length < 2 or args.length > 3
189
- abort("Usage: cli generate #{args[0]} <NAME> (--ruby or --python)")
190
- end
417
+ validate_language_inheriting_args!('target', args,
418
+ example: ['cli generate target EXAMPLE --ruby', 'Creates: targets/EXAMPLE/'],
419
+ docs: 'https://docs.openc3.com/docs/configuration/target')
191
420
 
192
421
  # Create the local variables
193
422
  target_name = args[1].upcase.gsub(/_+|-+/, '_')
@@ -259,33 +488,9 @@ RUBY
259
488
  end
260
489
 
261
490
  def self.generate_microservice(args)
262
- if args[1].nil? || args[1] == '--help' || args[1] == '-h'
263
- puts "Usage: cli generate microservice NAME (--ruby or --python)"
264
- puts ""
265
- puts "Generate a new microservice within an existing plugin"
266
- puts ""
267
- puts "Arguments:"
268
- puts " NAME Name of the microservice (required)"
269
- puts " Will be uppercased and underscores/hyphens converted to underscores"
270
- puts ""
271
- puts "Options:"
272
- puts " --ruby Generate Ruby microservice (or set OPENC3_LANGUAGE=ruby)"
273
- puts " --python Generate Python microservice (or set OPENC3_LANGUAGE=python)"
274
- puts " -h, --help Show this help message"
275
- puts ""
276
- puts "Example:"
277
- puts " cli generate microservice DATA_PROCESSOR --ruby"
278
- puts " Creates: microservices/DATA_PROCESSOR/"
279
- puts ""
280
- puts "Note: Must be run from within an existing plugin directory"
281
- puts ""
282
- puts "Documentation:"
283
- puts " https://docs.openc3.com/docs/configuration/plugins#microservices"
284
- exit(args[1].nil? ? 1 : 0)
285
- end
286
- if args.length < 2 or args.length > 3
287
- abort("Usage: cli generate #{args[0]} <NAME> (--ruby or --python)")
288
- end
491
+ validate_language_inheriting_args!('microservice', args,
492
+ example: ['cli generate microservice DATA_PROCESSOR --ruby', 'Creates: microservices/DATA_PROCESSOR/'],
493
+ docs: 'https://docs.openc3.com/docs/configuration/plugins#microservices')
289
494
 
290
495
  # Create the local variables
291
496
  microservice_name = args[1].upcase.gsub(/_+|-+/, '_')
@@ -331,32 +536,25 @@ RUBY
331
536
 
332
537
  def self.generate_widget(args)
333
538
  if args[1].nil? || args[1] == '--help' || args[1] == '-h'
334
- puts "Usage: cli generate widget NAME (--ruby or --python)"
335
- puts ""
336
- puts "Generate a new custom Vue.js widget within an existing plugin"
337
- puts ""
338
- puts "Arguments:"
339
- puts " NAME Name of the widget (required)"
340
- puts " Must be CapitalCase ending with 'Widget'"
341
- puts " Example: SuperdataWidget, StatusWidget"
342
- puts ""
343
- puts "Options:"
344
- puts " --ruby Generate Ruby plugin (or set OPENC3_LANGUAGE=ruby)"
345
- puts " --python Generate Python plugin (or set OPENC3_LANGUAGE=python)"
346
- puts " -h, --help Show this help message"
347
- puts ""
348
- puts "Example:"
349
- puts " cli generate widget SuperdataWidget --ruby"
350
- puts " Creates: src/SuperdataWidget.vue"
351
- puts ""
352
- puts "Note: Must be run from within an existing plugin directory"
353
- puts ""
354
- puts "Documentation:"
355
- puts " https://docs.openc3.com/docs/guides/custom-widgets"
356
- exit(args[1].nil? ? 1 : 0)
357
- end
358
- if args.length < 2 or args.length > 3
359
- abort("Usage: cli generate #{args[0]} <SuperdataWidget> (--ruby or --python)")
539
+ print_help(
540
+ usage: 'cli generate widget NAME',
541
+ description: 'Generate a new custom Vue.js widget within an existing plugin',
542
+ arguments: [
543
+ 'NAME Name of the widget (required)',
544
+ " Must be CapitalCase ending with 'Widget'",
545
+ ' Example: SuperdataWidget, StatusWidget',
546
+ ],
547
+ notes: 'Note: Widgets are JavaScript only. --ruby/--python flags are ignored.',
548
+ example: [
549
+ 'cli generate widget SuperdataWidget',
550
+ 'Creates: src/SuperdataWidget.vue',
551
+ ],
552
+ docs: 'https://docs.openc3.com/docs/guides/custom-widgets',
553
+ exit_code: (args[1].nil? ? 1 : 0),
554
+ )
555
+ end
556
+ if args.length != 2
557
+ abort("Usage: cli generate #{args[0]} <SuperdataWidget>")
360
558
  end
361
559
  # Per https://stackoverflow.com/a/47591707/453280
362
560
  if args[1] !~ /.*Widget$/ or args[1][0...-6] != args[1][0...-6].capitalize
@@ -403,8 +601,13 @@ RUBY
403
601
  def self.generate_tool(args)
404
602
  if args[1].nil? || args[1] == '--help' || args[1] == '-h'
405
603
  tool_type = args[0].to_s.downcase.gsub('-', '_')
604
+ common_args = [
605
+ 'TOOL NAME Display name of the tool (required, can include spaces)',
606
+ ' Will be converted to lowercase without spaces for directory name',
607
+ ]
608
+ common_note = 'Note: Tools are JavaScript only. --ruby/--python flags are ignored.'
609
+ docs_url = 'https://docs.openc3.com/docs/guides/custom-tools'
406
610
 
407
- # Specific help for tool variants
408
611
  if tool_type != 'tool'
409
612
  framework = case tool_type
410
613
  when 'tool_vue' then 'Vue.js'
@@ -413,65 +616,46 @@ RUBY
413
616
  when 'tool_svelte' then 'Svelte'
414
617
  else 'Custom'
415
618
  end
416
-
417
- puts "Usage: cli generate #{args[0]} 'TOOL NAME' (--ruby or --python)"
418
- puts ""
419
- puts "Generate a new #{framework} tool within an existing plugin"
420
- puts ""
421
- puts "Arguments:"
422
- puts " TOOL NAME Display name of the tool (required, can include spaces)"
423
- puts " Will be converted to lowercase without spaces for directory name"
424
- puts ""
425
- puts "Options:"
426
- puts " --ruby Generate Ruby plugin (or set OPENC3_LANGUAGE=ruby)"
427
- puts " --python Generate Python plugin (or set OPENC3_LANGUAGE=python)"
428
- puts " -h, --help Show this help message"
429
- puts ""
430
- puts "Example:"
431
- puts " cli generate #{args[0]} 'Data Viewer' --ruby"
432
- puts " Creates: tools/dataviewer/ (#{framework}-based)"
433
- puts ""
434
- puts "Note: Must be run from within an existing plugin directory"
435
- puts " For other tool types, see: cli generate tool --help"
436
- puts ""
437
- puts "Documentation:"
438
- puts " https://docs.openc3.com/docs/guides/custom-tools"
439
- exit(args[1].nil? ? 1 : 0)
619
+ print_help(
620
+ usage: "cli generate #{args[0]} 'TOOL NAME'",
621
+ description: "Generate a new #{framework} tool within an existing plugin",
622
+ arguments: common_args,
623
+ notes: common_note,
624
+ example: [
625
+ "cli generate #{args[0]} 'Data Viewer'",
626
+ "Creates: tools/dataviewer/ (#{framework}-based)",
627
+ ],
628
+ in_plugin_extra: 'For other tool types, see: cli generate tool --help',
629
+ docs: docs_url,
630
+ exit_code: (args[1].nil? ? 1 : 0),
631
+ )
440
632
  else
441
- # Generic help showing all types
442
- puts "Usage: cli generate #{args[0]} 'TOOL NAME' (--ruby or --python)"
443
- puts ""
444
- puts "Generate a new custom tool within an existing plugin"
445
- puts ""
446
- puts "Arguments:"
447
- puts " TOOL NAME Display name of the tool (required, can include spaces)"
448
- puts " Will be converted to lowercase without spaces for directory name"
449
- puts ""
450
- puts "Options:"
451
- puts " --ruby Generate Ruby plugin (or set OPENC3_LANGUAGE=ruby)"
452
- puts " --python Generate Python plugin (or set OPENC3_LANGUAGE=python)"
453
- puts " -h, --help Show this help message"
454
- puts ""
455
- puts "Tool Types:"
456
- puts " tool Generate Vue.js tool (default)"
457
- puts " tool_vue Generate Vue.js tool"
458
- puts " tool_angular Generate Angular tool"
459
- puts " tool_react Generate React tool"
460
- puts " tool_svelte Generate Svelte tool"
461
- puts ""
462
- puts "Example:"
463
- puts " cli generate tool 'Data Viewer' --ruby"
464
- puts " Creates: tools/dataviewer/"
465
- puts ""
466
- puts "Note: Must be run from within an existing plugin directory"
467
- puts ""
468
- puts "Documentation:"
469
- puts " https://docs.openc3.com/docs/guides/custom-tools"
470
- exit(args[1].nil? ? 1 : 0)
633
+ print_help(
634
+ usage: "cli generate #{args[0]} 'TOOL NAME'",
635
+ description: 'Generate a new custom tool within an existing plugin',
636
+ arguments: common_args,
637
+ notes: common_note,
638
+ extra_section: {
639
+ title: 'Tool Types',
640
+ lines: [
641
+ 'tool Generate Vue.js tool (default)',
642
+ 'tool_vue Generate Vue.js tool',
643
+ 'tool_angular Generate Angular tool',
644
+ 'tool_react Generate React tool',
645
+ 'tool_svelte Generate Svelte tool',
646
+ ],
647
+ },
648
+ example: [
649
+ "cli generate tool 'Data Viewer'",
650
+ 'Creates: tools/dataviewer/',
651
+ ],
652
+ docs: docs_url,
653
+ exit_code: (args[1].nil? ? 1 : 0),
654
+ )
471
655
  end
472
656
  end
473
- if args.length < 2 or args.length > 3
474
- abort("Usage: cli generate #{args[0]} 'Tool Name' (--ruby or --python)")
657
+ if args.length != 2
658
+ abort("Usage: cli generate #{args[0]} 'Tool Name'")
475
659
  end
476
660
 
477
661
  # Create the local variables
@@ -521,223 +705,63 @@ RUBY
521
705
  self.singleton_class.send(:alias_method, :generate_tool_svelte, :generate_tool)
522
706
 
523
707
  def self.generate_conversion(args)
524
- if args[1].nil? || args[2].nil? || args[1] == '--help' || args[1] == '-h'
525
- puts "Usage: cli generate conversion TARGET NAME (--ruby or --python)"
526
- puts ""
527
- puts "Generate a new conversion class for an existing target"
528
- puts ""
529
- puts "Arguments:"
530
- puts " TARGET Target name (required, must exist)"
531
- puts " NAME Conversion name (required)"
532
- puts " Will be uppercased with '_CONVERSION' suffix"
533
- puts ""
534
- puts "Options:"
535
- puts " --ruby Generate Ruby conversion (or set OPENC3_LANGUAGE=ruby)"
536
- puts " --python Generate Python conversion (or set OPENC3_LANGUAGE=python)"
537
- puts " -h, --help Show this help message"
538
- puts ""
539
- puts "Example:"
540
- puts " cli generate conversion EXAMPLE STATUS --ruby"
541
- puts " Creates: targets/EXAMPLE/lib/status_conversion.rb"
542
- puts ""
543
- puts "Note: Must be run from within an existing plugin directory"
544
- puts ""
545
- puts "Documentation:"
546
- puts " https://docs.openc3.com/docs/configuration/telemetry#read_conversion"
547
- exit(args[1].nil? || args[2].nil? ? 1 : 0)
548
- end
549
- if args.length < 3 or args.length > 4
550
- abort("Usage: cli generate conversion <TARGET> <NAME> (--ruby or --python)")
551
- end
552
-
553
- # Create the local variables
554
- target_name = args[1].upcase
555
- unless File.exist?("targets/#{target_name}")
556
- abort("Target '#{target_name}' does not exist! Conversions must be created for existing targets.")
557
- end
558
- conversion_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_CONVERSION"
559
- conversion_basename = "#{conversion_name.downcase}.#{@@language}"
560
- conversion_class = conversion_basename.filename_to_class_name
561
- conversion_class.inspect # Remove unused variable warning. These are used in binding for generator
562
- conversion_filename = "targets/#{target_name}/lib/#{conversion_basename}"
563
- if File.exist?(conversion_filename)
564
- abort("Conversion #{conversion_filename} already exists!")
565
- end
566
-
567
- process_template("#{TEMPLATES_DIR}/conversion", binding) do |filename|
568
- filename.sub!("conversion.#{@@language}", conversion_filename)
569
- false
570
- end
571
-
572
- puts "Conversion #{conversion_filename} successfully generated!"
573
- puts "To use the conversion add the following to a telemetry item:"
574
- puts " READ_CONVERSION #{conversion_basename}"
575
- return conversion_name
708
+ generate_target_artifact(args, {
709
+ suffix: 'CONVERSION',
710
+ kind: 'Conversion',
711
+ kind_plural: 'Conversions',
712
+ template: 'conversion',
713
+ template_source: 'conversion', # template file is conversion.rb / conversion.py
714
+ class_var: 'conversion_class',
715
+ usage_intro: 'To use the conversion add the following to a telemetry item:',
716
+ usage_directive: 'READ_CONVERSION %{basename}',
717
+ docs: 'https://docs.openc3.com/docs/configuration/telemetry#read_conversion',
718
+ help_example: 'STATUS',
719
+ })
576
720
  end
577
721
 
578
722
  def self.generate_processor(args)
579
- if args[1].nil? || args[2].nil? || args[1] == '--help' || args[1] == '-h'
580
- puts "Usage: cli generate processor TARGET NAME (--ruby or --python)"
581
- puts ""
582
- puts "Generate a new processor for an existing target"
583
- puts ""
584
- puts "Arguments:"
585
- puts " TARGET Target name (required, must exist)"
586
- puts " NAME Processor name (required)"
587
- puts " Will be uppercased with '_PROCESSOR' suffix"
588
- puts ""
589
- puts "Options:"
590
- puts " --ruby Generate Ruby processor (or set OPENC3_LANGUAGE=ruby)"
591
- puts " --python Generate Python processor (or set OPENC3_LANGUAGE=python)"
592
- puts " -h, --help Show this help message"
593
- puts ""
594
- puts "Example:"
595
- puts " cli generate processor EXAMPLE DATA --ruby"
596
- puts " Creates: targets/EXAMPLE/lib/data_processor.rb"
597
- puts ""
598
- puts "Note: Must be run from within an existing plugin directory"
599
- puts ""
600
- puts "Documentation:"
601
- puts " https://docs.openc3.com/docs/configuration/telemetry#processor"
602
- exit(args[1].nil? || args[2].nil? ? 1 : 0)
603
- end
604
- if args.length < 3 or args.length > 4
605
- abort("Usage: cli generate processor <TARGET> <NAME> (--ruby or --python)")
606
- end
607
-
608
- # Create the local variables
609
- target_name = args[1].upcase
610
- unless File.exist?("targets/#{target_name}")
611
- abort("Target '#{target_name}' does not exist! Processors must be created for existing targets.")
612
- end
613
- processor_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_PROCESSOR"
614
- processor_basename = "#{processor_name.downcase}.#{@@language}"
615
- processor_class = processor_basename.filename_to_class_name
616
- processor_class.inspect # Remove unused variable warning. These are used in binding for generator
617
- processor_filename = "targets/#{target_name}/lib/#{processor_basename}"
618
- if File.exist?(processor_filename)
619
- abort("Processor #{processor_filename} already exists!")
620
- end
621
-
622
- process_template("#{TEMPLATES_DIR}/processor", binding) do |filename|
623
- filename.sub!("processor.#{@@language}", processor_filename)
624
- false
625
- end
626
-
627
- puts "Processor #{processor_filename} successfully generated!"
628
- puts "To use the processor add the following to a telemetry packet:"
629
- puts " PROCESSOR #{args[2].upcase} #{processor_basename} <PARAMS...>"
630
- return processor_name
723
+ generate_target_artifact(args, {
724
+ suffix: 'PROCESSOR',
725
+ kind: 'Processor',
726
+ kind_plural: 'Processors',
727
+ template: 'processor',
728
+ template_source: 'processor',
729
+ class_var: 'processor_class',
730
+ usage_intro: 'To use the processor add the following to a telemetry packet:',
731
+ usage_directive: 'PROCESSOR %{name_upcase} %{basename} <PARAMS...>',
732
+ docs: 'https://docs.openc3.com/docs/configuration/telemetry#processor',
733
+ help_example: 'DATA',
734
+ })
631
735
  end
632
736
 
633
737
  def self.generate_limits_response(args)
634
- if args[1].nil? || args[2].nil? || args[1] == '--help' || args[1] == '-h'
635
- puts "Usage: cli generate limits_response TARGET NAME (--ruby or --python)"
636
- puts ""
637
- puts "Generate a new limits response for an existing target"
638
- puts ""
639
- puts "Arguments:"
640
- puts " TARGET Target name (required, must exist)"
641
- puts " NAME Limits response name (required)"
642
- puts " Will be uppercased with '_LIMITS_RESPONSE' suffix"
643
- puts ""
644
- puts "Options:"
645
- puts " --ruby Generate Ruby limits response (or set OPENC3_LANGUAGE=ruby)"
646
- puts " --python Generate Python limits response (or set OPENC3_LANGUAGE=python)"
647
- puts " -h, --help Show this help message"
648
- puts ""
649
- puts "Example:"
650
- puts " cli generate limits_response EXAMPLE CUSTOM --ruby"
651
- puts " Creates: targets/EXAMPLE/lib/custom_limits_response.rb"
652
- puts ""
653
- puts "Note: Must be run from within an existing plugin directory"
654
- puts ""
655
- puts "Documentation:"
656
- puts " https://docs.openc3.com/docs/configuration/telemetry#limits_response"
657
- exit(args[1].nil? || args[2].nil? ? 1 : 0)
658
- end
659
- if args.length < 3 or args.length > 4
660
- abort("Usage: cli generate limits_response <TARGET> <NAME> (--ruby or --python)")
661
- end
662
-
663
- # Create the local variables
664
- target_name = args[1].upcase
665
- unless File.exist?("targets/#{target_name}")
666
- abort("Target '#{target_name}' does not exist! Limits responses must be created for existing targets.")
667
- end
668
- response_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_LIMITS_RESPONSE"
669
- response_basename = "#{response_name.downcase}.#{@@language}"
670
- response_filename = "targets/#{target_name}/lib/#{response_basename}"
671
- response_class = response_basename.filename_to_class_name
672
- response_class.inspect # Remove unused variable warning. These are used in binding for generator
673
- if File.exist?(response_filename)
674
- abort("response #{response_filename} already exists!")
675
- end
676
-
677
- process_template("#{TEMPLATES_DIR}/limits_response", binding) do |filename|
678
- filename.sub!("response.#{@@language}", response_filename)
679
- false
680
- end
681
-
682
- puts "Limits response #{response_filename} successfully generated!"
683
- puts "To use the limits response add the following to a telemetry item:"
684
- puts " LIMITS_RESPONSE #{response_basename}"
685
- return response_name
738
+ generate_target_artifact(args, {
739
+ suffix: 'LIMITS_RESPONSE',
740
+ kind: 'Limits response',
741
+ kind_plural: 'Limits responses',
742
+ template: 'limits_response',
743
+ template_source: 'response', # template file is response.rb / response.py
744
+ class_var: 'response_class',
745
+ usage_intro: 'To use the limits response add the following to a telemetry item:',
746
+ usage_directive: 'LIMITS_RESPONSE %{basename}',
747
+ docs: 'https://docs.openc3.com/docs/configuration/telemetry#limits_response',
748
+ help_example: 'CUSTOM',
749
+ })
686
750
  end
687
751
 
688
752
  def self.generate_command_validator(args)
689
- if args[1].nil? || args[2].nil? || args[1] == '--help' || args[1] == '-h'
690
- puts "Usage: cli generate command_validator TARGET NAME (--ruby or --python)"
691
- puts ""
692
- puts "Generate a new command validator for an existing target"
693
- puts ""
694
- puts "Arguments:"
695
- puts " TARGET Target name (required, must exist)"
696
- puts " NAME Command validator name (required)"
697
- puts " Will be uppercased with '_COMMAND_VALIDATOR' suffix"
698
- puts ""
699
- puts "Options:"
700
- puts " --ruby Generate Ruby command validator (or set OPENC3_LANGUAGE=ruby)"
701
- puts " --python Generate Python command validator (or set OPENC3_LANGUAGE=python)"
702
- puts " -h, --help Show this help message"
703
- puts ""
704
- puts "Example:"
705
- puts " cli generate command_validator EXAMPLE RANGE --ruby"
706
- puts " Creates: targets/EXAMPLE/lib/range_command_validator.rb"
707
- puts ""
708
- puts "Note: Must be run from within an existing plugin directory"
709
- puts ""
710
- puts "Documentation:"
711
- puts " https://docs.openc3.com/docs/configuration/command#validator"
712
- exit(args[1].nil? || args[2].nil? ? 1 : 0)
713
- end
714
- if args.length < 3 or args.length > 4
715
- abort("Usage: cli generate command_validator <TARGET> <NAME> (--ruby or --python)")
716
- end
717
-
718
- # Create the local variables
719
- target_name = args[1].upcase
720
- unless File.exist?("targets/#{target_name}")
721
- abort("Target '#{target_name}' does not exist! Command validators must be created for existing targets.")
722
- end
723
- validator_name = "#{args[2].upcase.gsub(/_+|-+/, '_')}_COMMAND_VALIDATOR"
724
- validator_basename = "#{validator_name.downcase}.#{@@language}"
725
- validator_class = validator_basename.filename_to_class_name
726
- validator_class.inspect # Remove unused variable warning. These are used in binding for generator
727
- validator_filename = "targets/#{target_name}/lib/#{validator_basename}"
728
- if File.exist?(validator_filename)
729
- abort("Command validator #{validator_filename} already exists!")
730
- end
731
-
732
- process_template("#{TEMPLATES_DIR}/command_validator", binding) do |filename|
733
- filename.sub!("command_validator.#{@@language}", validator_filename)
734
- false
735
- end
736
-
737
- puts "Command validator #{validator_filename} successfully generated!"
738
- puts "To use the command validator add the following to a command:"
739
- puts " VALIDATOR #{validator_basename}"
740
- return validator_name
753
+ generate_target_artifact(args, {
754
+ suffix: 'COMMAND_VALIDATOR',
755
+ kind: 'Command validator',
756
+ kind_plural: 'Command validators',
757
+ template: 'command_validator',
758
+ template_source: 'command_validator',
759
+ class_var: 'validator_class',
760
+ usage_intro: 'To use the command validator add the following to a command:',
761
+ usage_directive: 'VALIDATOR %{basename}',
762
+ docs: 'https://docs.openc3.com/docs/configuration/command#validator',
763
+ help_example: 'RANGE',
764
+ })
741
765
  end
742
766
  end
743
767
  end