sod 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c23697cbcb0d37aa7c92b530d1952f535e8f8d338a1bfb1bc8a0f1279b589eb6
4
- data.tar.gz: 2cbeba3bf1b3a47507735469537db88a8f9ea47fe98277a4802816d25eaf7163
3
+ metadata.gz: 23b08587b0e2a98f391369e82409ccbfd1186efb54741a76b48c1b011d12a3bc
4
+ data.tar.gz: eb8792a76509485900d2dac190766214fa109d5aff8e0c582229fd8dbcdc92cd
5
5
  SHA512:
6
- metadata.gz: 003a046aacafec0865dd1b64b450b4fe3e4b1f6896fed015110f3df766a99f4c56fc5188fdd8c7fd1fdd94fb04cc8046b81fb67e2aeafaa6d311709cc488b7fb
7
- data.tar.gz: d4c84e4ab0c83cfd4a537e8584c6f62ad6d5b5af737a94244b8222f1dd0a93afb6b340befe1da7d7c7ffc869c7593428c5dcf1306e4ca3ee3132e8edec69c056
6
+ metadata.gz: 264edeb65dd9a578a7dbdf6cfbbb4d26fd3e39100c461ffde67ab20e61c4df5eb7c5cfd1b0eb088c9de7c45bf8b72939257a07e92c7ec4166c7749709648be0b
7
+ data.tar.gz: 883472c9fdb8b3392736e413e53a462be049d29bbce034195e9b6614fa9e018ace1690c685d976a30a2366a210d5cba12cc0eb2943476d5ba739fce2f3e348eb
checksums.yaml.gz.sig CHANGED
Binary file
data/README.adoc CHANGED
@@ -120,8 +120,8 @@ Notice, with only a few extra lines of code, you can build upon the initial _bla
120
120
  cli = Sod.new :demo, banner: "Demo 0.0.0: A demonstration." do
121
121
  on Sod::Prefabs::Actions::Help, self
122
122
 
123
- on :generate, "Generate project templates."
124
- on :db, "Manage database."
123
+ on "generate", "Generate project templates."
124
+ on "db", "Manage database."
125
125
  end
126
126
 
127
127
  cli.call
@@ -206,11 +206,11 @@ You've already seen some of the DSL syntax, via the earlier examples, but now we
206
206
  [source,ruby]
207
207
  ----
208
208
  Sod.new :demo, banner: "Demo 0.0.0: A demonstration." do
209
- on :db, "Manage database." do
209
+ on "db", "Manage database." do
210
210
  on Start
211
211
  on Stop
212
212
 
213
- on :structure, "Manage database structure." do
213
+ on "structure", "Manage database structure." do
214
214
  on Dump
215
215
  end
216
216
  end
@@ -220,7 +220,7 @@ Sod.new :demo, banner: "Demo 0.0.0: A demonstration." do
220
220
  end
221
221
  ----
222
222
 
223
- Despite the `Start`, `Stop`, and `Dump` actions not being implemented yet -- because you'll get a `NameError` if you try -- this does mean you'd have the following functionality available to you from the command line:
223
+ Despite the `Start`, `Stop`, and `Dump` actions not being implemented yet -- because you'll get a `NameError` if you try -- this does mean you'd eventually have the following functionality available from the command line:
224
224
 
225
225
  [source,bash]
226
226
  ----
@@ -240,7 +240,7 @@ Sod.new :demo, banner: "Demo 0.0.0: A demonstration." do
240
240
  end
241
241
  ----
242
242
 
243
- The first _positional_ argument (i.e. `DB`) is _always_ your action (don't worry, this'll be explained shortly), the second _positional_ argument is the first positional argument to the `DB.new` method followed by the `host` and `port` _keyword_ arguments. In other words, here's what's happening:
243
+ The first _positional_ argument (i.e. `DB`) is _always_ your action, the second _positional_ argument is the first positional argument to the `DB.new` method followed by the `host` and `port` _keyword_ arguments. In other words, here's what's happening:
244
244
 
245
245
  [source,ruby]
246
246
  ----
@@ -257,7 +257,8 @@ DB.new "MyDatabase", host: localhost, port: 5432
257
257
  This also means you get the following benefits:
258
258
 
259
259
  * Lazy initialization of your commands/actions.
260
- * Any positional and/or keyword arguments will be forwarded to your command/action. Blocks are excluded since they are used by the `on` method for nesting purposes.
260
+ * Quick injection of dependencies or customization of dependencies in general.
261
+ * Automatic forwarding of positional and/or keyword arguments to your command/action. Blocks are excluded since they are used by the `on` method for nesting purposes.
261
262
 
262
263
  To further understand the DSL, commands, and actions you'll need to start with actions since they are the building blocks.
263
264
 
@@ -265,73 +266,192 @@ To further understand the DSL, commands, and actions you'll need to start with a
265
266
 
266
267
  Actions are the lowest building blocks of the DSL which allow you to quickly implement, test, reuse, and compose more complex architectures. They provide a nice layer atop native `OptionParser#on` functionality.
267
268
 
268
- There are two kinds of actions: custom and prefabricated. We'll start with custom actions and explore prefabricated actions later. Custom actions allow you to define your own functionality by inheriting from `Sod::Action` and leveraging the DSL that comes with it. Here's a high level breakdown of the macros you can use:
269
+ There are two kinds of actions: custom and prefabricated. We'll start with custom actions and explore prefabricated actions later. Custom actions allow you to define your own functionality by inheriting from `Sod::Action` and leveraging the DSL that comes with it.
270
+
271
+ ===== Macros
272
+
273
+ Here's a high level breakdown of the macros you can use:
269
274
 
270
275
  * `description`: Optional (but strongly encouraged). Allows you to describe your action and appears within help documentation. If the description is not defined, then only your action's handle (i.e. aliases) will be shown.
271
276
  * `ancillary`: Optional. Allows you to provide supplemental text in addition to your description that might be helpful to know about when displaying help documentation. This can accept single or multiple arguments. Order matters since each argument will appear on a separate line in the order listed.
272
- * `on`: Required. Allows you to define the behavior of your action through keyword arguments. Otherwise, if not defined, the action will be ignored. This macro mimics {option_parser_link} `#on` behavior via the following positional and keyword arguments:
277
+ * `on`: Required. Allows you to define the behavior of your action through keyword arguments. Otherwise, if not defined, you'll get a `Sod::Error` telling you that you must, at a minimum, define some aliases. This macro mimics {option_parser_link} `#on` behavior via the following positional and keyword arguments:
273
278
  ** `aliases`: Required. This is a positional argument and defines the short and long form aliases of your action. Your aliases can be a single string (i.e. `on "--version"`) or an array of short and long form aliases. For example, using `on %w[-v --version]` would allow you to use `-v` or `--version` from the command line to call your action. You can also use boolean aliases such as `--build` or `--[no-]build` which the option parser will supply to your `#call` method as a boolean value.
274
279
  ** `argument`: Optional. Serves as documentation, must be a string value, and allows the {option_parser_link} to determine if the argument is required or optional. As per the {option_parser_link} documentation, you could use the following values for example:
275
280
  *** `TEXT`: Required text.
276
281
  *** `[TEXT]`: Optional text.
277
282
  *** `a,b,c`: Required list.
278
283
  *** `[a,b,c]`: Optional list.
279
- ** `type`: Optional. The type is inferred from your argument but, if you need to be explicit or want to use a custom type not supported by the option parser by default, you can specify the type by providing a primitive. Example: `String`, `Array`, `Hash`, `Date`, etc. You can also use custom types, as provided by this gem, or your own custom type. See {option_parser_link} documentation for details.
284
+ ** `type`: Optional. The type is inferred from your argument but, if you need to be explicit or want to use a custom type not supported by default by option parser, you can specify the type by providing a primitive. Example: `String`, `Array`, `Hash`, `Date`, etc. You can also use custom types, provided by this gem and explained later, or implement your own.
280
285
  ** `allow`: Optional. Allows you to define what values are allowed as defined via the `argument` or `type` keywords. This can be a string, array, hash, etc. as long as it's compatible with what is defined via the `argument` and/or `type` keyword. This information will also show up in the help documentation as well.
281
286
  ** `default`: Optional. Allows you to supply a default value and is a handy for simple values which don't require lazy evaluation via the corresponding default macro. ⚠️ This is ignored if the corresponding macro is used so ensure you use one or the other but not both.
282
287
  ** `description`: Optional. Allows you to define a description. Handy for short descriptions that can fit on a single line. Otherwise, for longer descriptions, use the macro. ⚠️ This is ignored if the corresponding macro is used so ensure you use one or the other but not both.
283
288
  ** `ancillary`: Optional. Allows you to define ancillary text to supplement your description. It can accept a string or an array. Handy for short, supplementary, text that can fit on a single line. Otherwise, for more verbose details, use the macro. ⚠️ This is ignored if the corresponding macro is used so ensure you use one or the other but not both.
284
- * `default`: Optional. Uses a block, which is lazy evaluated, so you can define a default value. This is most helpful when used in combination with an _optional_ `argument` and/or `type` which can fallback to a safe default. This information shows up in the help text where the value will be rendered green text. In the case of booleans, they will be rendered as green for `true` and red for `false`.
289
+ * `default`: Optional. Uses a block which lazy evaluates and resolves your value. This is most helpful when used in combination with an _optional_ `argument` and/or `type` which can fallback to a safe default. This information shows up in the help text where the value is rendered as green text. In the case of booleans, they will be rendered as green for `true` and red for `false`.
290
+
291
+ With the above in mind, let's look at a few examples of what you can do when you put all of this together.
292
+
293
+ ===== Booleans
285
294
 
286
- At a minimum, you want to define the `description` and `on` macros while implementing the `#call` message. Here's an example of implementing an action that echoes input as output:
295
+ Boolean flags are long alases only, take _no arguments_, and use `[no-]` syntax after the double dashes. Here's a minimal implementation:
287
296
 
288
297
  [source,ruby]
289
298
  ----
290
- class Echo < Sod::Action
291
- description "Echo input as output."
299
+ class Demo < Sod::Action
300
+ on "--[no-]run"
301
+
302
+ def call(value) = puts "Got: #{value}"
303
+ end
304
+
305
+ cli = Sod.new { on Demo }
306
+
307
+ cli.call %w[--run] # "Got: true"
308
+ cli.call %w[--no-run] # "Got: false"
309
+ ----
310
+
311
+ Because a value is always provided when using a boolean flag, you can make it a required positional parameter via your method definition (i.e. `call(value)`). You don't need to worry about type safety because {option_parser_link} will either pass in a `true` or `false` value as you can see from the output above.
312
+
313
+ ===== Arguments
292
314
 
315
+ Arguments inform {option_parser_link} how to parse values as either _optional_ or _required_. Here's minimal implementation of an optional argument:
316
+
317
+ [source,ruby]
318
+ ----
319
+ class Demo < Sod::Action
320
+ on %w[-e --echo], argument: "[TEXT]"
321
+
322
+ def call(text = nil) = puts "Got: #{text}"
323
+ end
324
+
325
+ cli = Sod.new { on Demo }
326
+
327
+ cli.call %w[-e] # "Got: "
328
+ cli.call %w[--echo] # "Got: "
329
+ cli.call %w[-e hi] # "Got: hi"
330
+ cli.call %w[--echo hi] # "Got: hi"
331
+ ----
332
+
333
+ The method definition of `call(text = nil)` is important because if you call the action directly you'd want to have a safe default that mirrors the `on` macro. You could provide a non-nil default but we'll discuss this more later. You could also use a `call(text)` method definition since {option_parser_link} will always give you a value even if it is `nil`. You can see see how this behavior plays out in the examples above. On the flip side, when you need a _required_ argument, simply drop the brackets (i.e. `[]`). Here's an example:
334
+
335
+ [source,ruby]
336
+ ----
337
+ class Demo < Sod::Action
293
338
  on %w[-e --echo], argument: "TEXT"
294
339
 
295
- def call(text) = puts text
340
+ def call(text) = puts "Got: #{text}"
296
341
  end
297
342
 
298
- cli = Sod.new :demo, banner: "Demo 0.0.0: A demonstration" do
299
- on Echo
300
- on Sod::Prefabs::Actions::Help, self
343
+ cli = Sod.new { on Demo }
344
+
345
+ cli.call %w[-e] # "🛑 Missing argument: -e"
346
+ cli.call %w[--echo] # "🛑 Missing argument: --echo"
347
+ cli.call %w[-e hi] # "Got: hi"
348
+ cli.call %w[--echo hi] # "Got: hi"
349
+ ----
350
+
351
+ There are only three major differences between the earlier _optional_ example and the above _required_ example:
352
+
353
+ * The argument is required because it's not wrapped in brackets.
354
+ * The method definition requires a `text` parameter.
355
+ * You get an error when not providing an argument.
356
+
357
+ ===== Types
358
+
359
+ Types are optional but worth having when you need the safety check. Here's a minimal example:
360
+
361
+ [source,ruby]
362
+ ----
363
+ class Demo < Sod::Action
364
+ on %w[-e --echo], argument: "NUMBER", type: Float
365
+
366
+ def call(number) = puts "Got: #{number}"
301
367
  end
368
+
369
+ cli = Sod.new { on Demo }
370
+
371
+ cli.call %w[--echo 123] # "Got: 123.0"
372
+ cli.call %w[--echo 1.5] # "Got: 1.5"
373
+ cli.call %w[--echo hi] # 🛑 Invalid argument: --echo hi
302
374
  ----
303
375
 
304
- If we run the above implementation with different inputs, we'll get multiple outputs:
376
+ Notice the type is a `Float` where only the first two examples work but the last one ends in an error because {option_parser_link} can't cast the raw input to a float.
377
+
378
+ ===== Allows
379
+
380
+ Allows give you the ability to define what is acceptable as input and need to match your type (if you supply one). Here's a minimal example:
305
381
 
306
382
  [source,ruby]
307
383
  ----
308
- cli.call
384
+ class Demo < Sod::Action
385
+ on %w[-e --echo], argument: "TEXT", allow: %w[hi hello]
309
386
 
310
- # Demo 0.0.0: A demonstration
311
- #
312
- # USAGE
313
- # demo [OPTIONS]
314
- #
315
- # OPTIONS
316
- # -e, --echo TEXT Echo input as output.
317
- # -h, --help [COMMAND] Show this message.
318
- #
319
- # cli.call %w[--echo hello]
387
+ def call(text) = puts "Got: #{text}"
388
+ end
320
389
 
321
- cli.call %w[--echo hello]
390
+ cli = Sod.new { on Demo }
322
391
 
323
- # hello
392
+ cli.call %w[--echo hi] # "Got: hi"
393
+ cli.call %w[--echo hello] # "Got: hello"
394
+ cli.call %w[--echo test] # "🛑 Invalid argument: --echo test"
395
+ ----
324
396
 
325
- cli.call %s[--e hello]
397
+ Here you can see the first two examples pass while the last one fails because `"test"` isn't a valid value within the allowed array.
326
398
 
327
- # hello
399
+ ===== Defaults
328
400
 
329
- cli.call ["--echo"]
401
+ Defaults are not something that {option_parser_link} supports out-of-the-box but are handy for documentation purposes and within your implementation as fallback values. Here's a minimal example:
402
+
403
+ [source,ruby]
404
+ ----
405
+ class Demo < Sod::Action
406
+ on %w[-e --echo], argument: "[TEXT]", default: "fallback"
407
+
408
+ def call(text = nil) = puts "Got: #{text || default}"
409
+ end
410
+
411
+ cli = Sod.new { on Demo }
412
+
413
+ cli.call %w[--echo] # "Got: fallback"
414
+ cli.call %w[--echo hi] # "Got: hi"
415
+ ----
330
416
 
331
- # 🛑 Missing argument for: --echo.
417
+ Notice how the default is printed when no value is given but is overwritten when an actual value is supplied. This is the correct way to handle defaults but might not be what you are used to. If you're thinking that you'd rather write the implementation like this:
418
+
419
+ [source,ruby]
420
+ ----
421
+ def call(text = default) = puts "Got: #{text}"
422
+ ----
423
+
424
+ ...you'd not be wrong. In fact, if you initialized and called the action, you'd get what you'd expect:
425
+
426
+ [source,ruby]
332
427
  ----
428
+ demo = Demo.new
333
429
 
334
- As you can see, the description shows up in the help text while the `-e` and `--echo` aliases can be used interchangeably. We only get the missing argument error when the argument (i.e. `"TEXT"`) isn't supplied. Finally, when the action is called, we see that the `"hello"` text is outputted to the console for use. Here's an updated version of the above implementation which leverages all features:
430
+ demo.call # "Got: fallback"
431
+ demo.call "hi" # "Got: hi"
432
+ ----
433
+
434
+ The reason the above is a problem is because {option_parser_link} ignores _optional_ parameters and all keywords. Here's the fully modified example:
435
+
436
+ [source,ruby]
437
+ ----
438
+ class Demo < Sod::Action
439
+ on %w[-e --echo], argument: "[TEXT]", default: "fallback"
440
+
441
+ def call(text = default) = puts "Got: #{text}"
442
+ end
443
+
444
+ cli = Sod.new { on Demo }
445
+
446
+ cli.call %w[--echo] # "Got: "
447
+ cli.call %w[--echo hi] # "Got: hi"
448
+ ----
449
+
450
+ Notice how there is surprising behavior with the first result (i.e. an empty string). This is because when {option_parser_link} completely ignores the value of the _optional_ parameter. I've logged an link:https://github.com/ruby/optparse/issues/55[issue] if you want to know more. For now, be aware of this quirk as it can be confusing if you are not familiar with {option_parser_link}.
451
+
452
+ ===== Examples
453
+
454
+ The following are a few more examples, in case it helps, with the first leveraging all features:
335
455
 
336
456
  [source,ruby]
337
457
  ----
@@ -406,7 +526,7 @@ cli.call ["--echo"]
406
526
  # `Echo#call [[:rest, :*]]` must be implemented. (NotImplementedError)
407
527
  ----
408
528
 
409
- At a minimum, as shown from the error above, your `#call` method needs to allow the forwarding of positional arguments which means you can use `def call(*)` if you want to ignore arguments or define which arguments you care about and ignore the rest. Up to you. Also, _all_ of the information defined within your action is available to you within the instance. Here's an example action which inspects itself:
529
+ At a minimum, your `#call` method needs to allow the forwarding of positional arguments which means you can use `def call(*)` if you want to ignore arguments or define which arguments you care about and ignore the rest. Up to you. Also, _all_ of the information defined within your action is available to you within the instance. Here's an example action which inspects itself:
410
530
 
411
531
  [source,ruby]
412
532
  ----
@@ -484,7 +604,7 @@ require "bundler/inline"
484
604
 
485
605
  gemfile true do
486
606
  source "https://rubygems.org"
487
- gem "sod", path: "~/Engineering/OSS/sod"
607
+ gem "sod"
488
608
  end
489
609
 
490
610
  class One < Sod::Action
@@ -574,8 +694,8 @@ Inline commands provide a lightweight way to namespace your actions when you don
574
694
 
575
695
  [source,ruby]
576
696
  ----
577
- cli = Sod.new :demo, banner: "Demo 0.0.0: A demonstration" do
578
- on :demo, "A demonstration command." do
697
+ cli = Sod.new banner: "Demo 0.0.0: A demonstration" do
698
+ on "demo", "A demonstration command." do
579
699
  on One
580
700
  on Two
581
701
  end
@@ -588,8 +708,8 @@ Inline commands can have ancillary text by passing in additional arguments _afte
588
708
 
589
709
  [source,ruby]
590
710
  ----
591
- cli = Sod.new :demo, banner: "Demo 0.0.0: A demonstration" do
592
- on :demo, "A demonstration command.", "Some text.", "Some more text."
711
+ cli = Sod.new banner: "Demo 0.0.0: A demonstration" do
712
+ on "demo", "A demonstration command.", "Some text.", "Some more text."
593
713
  end
594
714
  ----
595
715
 
@@ -604,7 +724,7 @@ A _reusable_ command is what you saw earlier where you can subclass from `Sod::C
604
724
  [source,ruby]
605
725
  ----
606
726
  class Demo < Sod::Command
607
- handle :demo
727
+ handle "demo"
608
728
 
609
729
  description "A demonstration command."
610
730
 
@@ -672,7 +792,7 @@ The override is handy for situations where you have a value (first argument) tha
672
792
  ----
673
793
  context = Sod::Context[defaults_path: "path/to/defaults.yml" version_label: "Demo 0.0.0"]
674
794
 
675
- Sod.new :demo, banner: "A demonstration." do
795
+ Sod.new banner: "A demonstration." do
676
796
  on(Sod::Prefabs::Commands::Config, context:)
677
797
  on(Sod::Prefabs::Actions::Version, context:)
678
798
  on Sod::Prefabs::Actions::Help, self
@@ -683,7 +803,7 @@ end
683
803
 
684
804
  ==== Types
685
805
 
686
- Types are a way to extend default {option_parser_link} functionality. Two types -- not provided by {option_parser_link} -- worth being aware of are:
806
+ Types are a way to extend default {option_parser_link} functionality. Here are a few types -- not provided by {option_parser_link} -- worth knowing about:
687
807
 
688
808
  **Pathname**
689
809
 
@@ -715,6 +835,27 @@ class Demo < Sod::Action
715
835
  end
716
836
  ----
717
837
 
838
+ **Custom**
839
+
840
+ Creating a custom type requires minimal effort and can be implemented in only a few files:
841
+
842
+ [source,ruby]
843
+ ----
844
+ # lib/my_type.rb
845
+
846
+ MyType = -> value { # Implementation details go here. }
847
+ ----
848
+
849
+ [source,ruby]
850
+ ----
851
+ # lib/extensions/option_parser.rb
852
+ require "optparse"
853
+
854
+ OptionParser.accept(MyType) { |value| MyType.call value }
855
+ ----
856
+
857
+ Once you've implemented a custom type, you are then free to require and reference it within the DSL.
858
+
718
859
  ==== Prefabrications
719
860
 
720
861
  Several pre-built commands and actions are provided for you as foundational tooling to get you up and running quickly. You can use and customize them as desired.
@@ -757,7 +898,7 @@ This action is most useful when building customizable CLIs where you want users
757
898
 
758
899
  ===== Help
759
900
 
760
- By now you should be familiar with the help action which allows you to print CLI documentation for users of your CLI. This action consumes the entire graph (i.e. `self`) of information in order to render documentation. You'll want to add this by default or customize with your own help action should you not like the default functionality. Anything is possible. Here's how to use:
901
+ By now you should be familiar with the help action which allows you to print CLI documentation for users of your CLI. This action consumes the entire graph (i.e. `self`) of information in order to render documentation. You'll want to add this by default or customize with your own help action should you not like the default functionality. Anything is possible. Here's some usage:
761
902
 
762
903
  [source,ruby]
763
904
  ----
@@ -845,7 +986,7 @@ class Echo < Sod::Action
845
986
  end
846
987
 
847
988
  cli = Sod.new :demo, banner: "Demo 0.0.0: A demonstration." do
848
- on :db, "Manage database." do
989
+ on "db", "Manage database." do
849
990
  on Start
850
991
  on Stop
851
992
  end
data/sod.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "sod"
5
- spec.version = "0.1.0"
5
+ spec.version = "0.1.1"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://alchemists.io/projects/sod"
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sod
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brooke Kuhlmann
@@ -35,7 +35,7 @@ cert_chain:
35
35
  3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
36
36
  gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
37
37
  -----END CERTIFICATE-----
38
- date: 2023-06-20 00:00:00.000000000 Z
38
+ date: 2023-06-23 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: cogger
metadata.gz.sig CHANGED
Binary file