command_kit 0.1.0.pre1

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.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.github/workflows/ruby.yml +29 -0
  4. data/.gitignore +7 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +29 -0
  8. data/Gemfile +14 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +283 -0
  11. data/Rakefile +23 -0
  12. data/command_kit.gemspec +60 -0
  13. data/gemspec.yml +14 -0
  14. data/lib/command_kit.rb +1 -0
  15. data/lib/command_kit/arguments.rb +161 -0
  16. data/lib/command_kit/arguments/argument.rb +111 -0
  17. data/lib/command_kit/arguments/argument_value.rb +81 -0
  18. data/lib/command_kit/arguments/usage.rb +6 -0
  19. data/lib/command_kit/colors.rb +355 -0
  20. data/lib/command_kit/command.rb +42 -0
  21. data/lib/command_kit/command_name.rb +95 -0
  22. data/lib/command_kit/commands.rb +299 -0
  23. data/lib/command_kit/commands/auto_load.rb +153 -0
  24. data/lib/command_kit/commands/auto_load/subcommand.rb +90 -0
  25. data/lib/command_kit/commands/auto_require.rb +138 -0
  26. data/lib/command_kit/commands/command.rb +12 -0
  27. data/lib/command_kit/commands/help.rb +43 -0
  28. data/lib/command_kit/commands/parent_command.rb +21 -0
  29. data/lib/command_kit/commands/subcommand.rb +51 -0
  30. data/lib/command_kit/console.rb +141 -0
  31. data/lib/command_kit/description.rb +89 -0
  32. data/lib/command_kit/env.rb +43 -0
  33. data/lib/command_kit/env/home.rb +71 -0
  34. data/lib/command_kit/env/path.rb +71 -0
  35. data/lib/command_kit/examples.rb +99 -0
  36. data/lib/command_kit/exception_handler.rb +55 -0
  37. data/lib/command_kit/help.rb +62 -0
  38. data/lib/command_kit/help/man.rb +125 -0
  39. data/lib/command_kit/inflector.rb +84 -0
  40. data/lib/command_kit/main.rb +103 -0
  41. data/lib/command_kit/options.rb +179 -0
  42. data/lib/command_kit/options/option.rb +171 -0
  43. data/lib/command_kit/options/option_value.rb +90 -0
  44. data/lib/command_kit/options/parser.rb +227 -0
  45. data/lib/command_kit/options/quiet.rb +53 -0
  46. data/lib/command_kit/options/usage.rb +6 -0
  47. data/lib/command_kit/options/verbose.rb +55 -0
  48. data/lib/command_kit/options/version.rb +62 -0
  49. data/lib/command_kit/os.rb +47 -0
  50. data/lib/command_kit/pager.rb +115 -0
  51. data/lib/command_kit/printing.rb +32 -0
  52. data/lib/command_kit/printing/indent.rb +78 -0
  53. data/lib/command_kit/program_name.rb +57 -0
  54. data/lib/command_kit/stdio.rb +138 -0
  55. data/lib/command_kit/usage.rb +102 -0
  56. data/lib/command_kit/version.rb +4 -0
  57. data/lib/command_kit/xdg.rb +138 -0
  58. data/spec/arguments/argument_spec.rb +169 -0
  59. data/spec/arguments/argument_value_spec.rb +126 -0
  60. data/spec/arguments_spec.rb +213 -0
  61. data/spec/colors_spec.rb +470 -0
  62. data/spec/command_kit_spec.rb +8 -0
  63. data/spec/command_name_spec.rb +130 -0
  64. data/spec/command_spec.rb +49 -0
  65. data/spec/commands/auto_load/subcommand_spec.rb +82 -0
  66. data/spec/commands/auto_load_spec.rb +128 -0
  67. data/spec/commands/auto_require_spec.rb +142 -0
  68. data/spec/commands/fixtures/test_auto_load/cli/commands/test1.rb +10 -0
  69. data/spec/commands/fixtures/test_auto_load/cli/commands/test2.rb +10 -0
  70. data/spec/commands/fixtures/test_auto_require/lib/test_auto_require/cli/commands/test1.rb +10 -0
  71. data/spec/commands/help_spec.rb +66 -0
  72. data/spec/commands/parent_command_spec.rb +40 -0
  73. data/spec/commands/subcommand_spec.rb +99 -0
  74. data/spec/commands_spec.rb +767 -0
  75. data/spec/console_spec.rb +201 -0
  76. data/spec/description_spec.rb +203 -0
  77. data/spec/env/home_spec.rb +46 -0
  78. data/spec/env/path_spec.rb +78 -0
  79. data/spec/env_spec.rb +123 -0
  80. data/spec/examples_spec.rb +235 -0
  81. data/spec/exception_handler_spec.rb +103 -0
  82. data/spec/help_spec.rb +119 -0
  83. data/spec/inflector_spec.rb +104 -0
  84. data/spec/main_spec.rb +179 -0
  85. data/spec/options/option_spec.rb +258 -0
  86. data/spec/options/option_value_spec.rb +67 -0
  87. data/spec/options/parser_spec.rb +265 -0
  88. data/spec/options_spec.rb +137 -0
  89. data/spec/os_spec.rb +46 -0
  90. data/spec/pager_spec.rb +154 -0
  91. data/spec/printing/indent_spec.rb +130 -0
  92. data/spec/printing_spec.rb +76 -0
  93. data/spec/program_name_spec.rb +62 -0
  94. data/spec/spec_helper.rb +6 -0
  95. data/spec/stdio_spec.rb +264 -0
  96. data/spec/usage_spec.rb +237 -0
  97. data/spec/xdg_spec.rb +191 -0
  98. metadata +156 -0
@@ -0,0 +1,90 @@
1
+ require 'command_kit/commands/subcommand'
2
+
3
+ module CommandKit
4
+ module Commands
5
+ class AutoLoad < Module
6
+ class Subcommand < Commands::Subcommand
7
+
8
+ # The fully qualified constant of the command class.
9
+ #
10
+ # @return [String]
11
+ attr_reader :constant
12
+
13
+ # The path to the file containing the command class.
14
+ #
15
+ # @return [String]
16
+ attr_reader :path
17
+
18
+ # A short summary for the sub-command.
19
+ #
20
+ # @return [String, nil]
21
+ attr_reader :summary
22
+
23
+ #
24
+ # Initializes the lazy-loaded subcommand.
25
+ #
26
+ # @param [String] path
27
+ #
28
+ # @param [String] constant
29
+ #
30
+ # @param [String, nil] summary
31
+ # A short summary for the subcommand.
32
+ #
33
+ # @param [Hash{Symbol => Object}] kwargs
34
+ # Keyword arguments.
35
+ #
36
+ # @option kwargs [Array<String>] aliases
37
+ # Optional alias names for the subcommand.
38
+ #
39
+ def initialize(constant, path, summary: nil, **kwargs)
40
+ @constant = constant
41
+ @path = path
42
+
43
+ super(nil, summary: summary, **kwargs)
44
+ end
45
+
46
+ #
47
+ # Requires the file.
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ def require!
52
+ require(@path)
53
+ end
54
+
55
+ #
56
+ # Resolves the {#constant} for the command class.
57
+ #
58
+ # @return [Class]
59
+ # The command class.
60
+ #
61
+ # @raise [NameError]
62
+ # The command class could not be found.
63
+ #
64
+ def const_get
65
+ Object.const_get("::#{@constant}",false)
66
+ end
67
+
68
+ #
69
+ # Lazy-loads the command class.
70
+ #
71
+ # @return [Class]
72
+ # The command class.
73
+ #
74
+ # @raise [LoadError]
75
+ # Could not load the given {#path}.
76
+ #
77
+ # @raise [NameError]
78
+ # Could not resolve the {#constant} for the command class.
79
+ #
80
+ def command
81
+ @command ||= (
82
+ require!
83
+ const_get
84
+ )
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,138 @@
1
+ require 'command_kit/commands'
2
+ require 'command_kit/commands/subcommand'
3
+ require 'command_kit/inflector'
4
+
5
+ module CommandKit
6
+ module Commands
7
+ #
8
+ # Adds a catch-all that attempts to load missing commands from a
9
+ # directory/namespace.
10
+ #
11
+ # ## Examples
12
+ #
13
+ # module Foo
14
+ # class CLI
15
+ #
16
+ # include CommandKit::Commands
17
+ # include CommandKit::Commands::AutoRequire.new(
18
+ # dir: 'foo/bar/commands',
19
+ # namespace: 'Foo::CLI::Commands'
20
+ # )
21
+ #
22
+ # end
23
+ # end
24
+ #
25
+ class AutoRequire < Module
26
+
27
+ # The directory to attempt to require command files within.
28
+ #
29
+ # @return [String]
30
+ attr_reader :dir
31
+
32
+ # The namespace to lookup command classes within.
33
+ #
34
+ # @return [String]
35
+ attr_reader :namespace
36
+
37
+ #
38
+ # Initializes.
39
+ #
40
+ # @param [String] dir
41
+ # The directory to require commands from.
42
+ #
43
+ # @param [String] namespace
44
+ # The namespace to search for command classes in.
45
+ #
46
+ def initialize(dir: , namespace: )
47
+ @dir = dir
48
+ @namespace = namespace
49
+ end
50
+
51
+ #
52
+ # Returns the path for the given command name.
53
+ #
54
+ # @param [String] name
55
+ # The given command name.
56
+ #
57
+ # @return [String]
58
+ # The path to the file that should contain the command.
59
+ #
60
+ def join(name)
61
+ File.join(@dir,name)
62
+ end
63
+
64
+ #
65
+ # Requires a file within the {#dir}.
66
+ #
67
+ # @param [String] file_name
68
+ #
69
+ # @return [Boolean]
70
+ #
71
+ # @raise [LoadError]
72
+ #
73
+ def require(file_name)
74
+ super(join(file_name))
75
+ end
76
+
77
+ #
78
+ # Resolves the constant for the command class within the {#namespace}.
79
+ #
80
+ # @param [String] constant
81
+ # The constant name.
82
+ #
83
+ # @return [Class]
84
+ # The command class.
85
+ #
86
+ # @raise [NameError]
87
+ # The command class could not be found within the {#namespace}.
88
+ #
89
+ def const_get(constant)
90
+ Object.const_get("::#{@namespace}::#{constant}",false)
91
+ end
92
+
93
+ #
94
+ # Attempts to load the command from the {#dir} and {#namespace}.
95
+ #
96
+ # @param [String] command_name
97
+ # The given command name.
98
+ #
99
+ # @return [Class, nil]
100
+ # The command's class, or `nil` if the command cannot be loaded from
101
+ # {#dir} or found within {#namespace}.
102
+ #
103
+ def command(command_name)
104
+ file_name = Inflector.underscore(command_name)
105
+
106
+ begin
107
+ require(file_name)
108
+ rescue LoadError
109
+ return
110
+ end
111
+
112
+ constant = Inflector.camelize(file_name)
113
+
114
+ begin
115
+ const_get(constant)
116
+ rescue NameError
117
+ return
118
+ end
119
+ end
120
+
121
+ #
122
+ # Includes {Commands} and adds a default proc to
123
+ # {Commands::ClassMethods#commands .commands}.
124
+ #
125
+ # @param [Class] command
126
+ # The command class including {AutoRequire}.
127
+ #
128
+ def included(command)
129
+ command.include Commands
130
+ command.commands.default_proc = ->(hash,key) {
131
+ hash[key] = if (command_class = command(key))
132
+ Commands::Subcommand.new(command_class)
133
+ end
134
+ }
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,12 @@
1
+ require 'command_kit/command'
2
+ require 'command_kit/commands/parent_command'
3
+
4
+ module CommandKit
5
+ module Commands
6
+ class Command < CommandKit::Command
7
+
8
+ include ParentCommand
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'command_kit/command'
4
+ require 'command_kit/commands/parent_command'
5
+
6
+ module CommandKit
7
+ module Commands
8
+ #
9
+ # The default help command.
10
+ #
11
+ class Help < Command
12
+
13
+ include ParentCommand
14
+
15
+ argument :command, desc: 'Command name to lookup'
16
+
17
+ #
18
+ # Prints the given commands `--help` output or lists registered commands.
19
+ #
20
+ # @param [String, nil] command
21
+ # The given command name, or `nil` if no command name was given.
22
+ #
23
+ def run(command=nil)
24
+ case command
25
+ when nil
26
+ parent_command.help
27
+ else
28
+ if (subcommand = parent_command.command(command))
29
+ unless subcommand.respond_to?(:help)
30
+ raise(TypeError,"#{subcommand.inspect} must define a #help method")
31
+ end
32
+
33
+ subcommand.help
34
+ else
35
+ print_error "#{command_name}: unknown command: #{command}"
36
+ exit(1)
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ module CommandKit
2
+ module Commands
3
+ module ParentCommand
4
+
5
+ # The parent command instance.
6
+ #
7
+ # @return [Object<Commands>]
8
+ attr_reader :parent_command
9
+
10
+ #
11
+ # Initializes the command and sets {#parent_command}.
12
+ #
13
+ def initialize(parent_command: , **kwargs)
14
+ @parent_command = parent_command
15
+
16
+ super(**kwargs)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,51 @@
1
+ module CommandKit
2
+ module Commands
3
+ class Subcommand
4
+
5
+ # The command class.
6
+ #
7
+ # @return [Class]
8
+ attr_reader :command
9
+
10
+ # A short summary for the subcommand.
11
+ #
12
+ # @return [String, nil]
13
+ attr_reader :summary
14
+
15
+ # Optional alias names for the subcommand.
16
+ #
17
+ # @return [Array<String>]
18
+ attr_reader :aliases
19
+
20
+ #
21
+ # Initializes the subcommand.
22
+ #
23
+ # @param [Class] command
24
+ # The command class.
25
+ #
26
+ # @param [String, nil] summary
27
+ # A short summary for the subcommand. Defaults to the first sentence
28
+ # of the command.
29
+ #
30
+ # @param [Array<String>] aliases
31
+ # Optional alias names for the subcommand.
32
+ #
33
+ def initialize(command, summary: self.class.summary(command),
34
+ aliases: [])
35
+ @command = command
36
+ @summary = summary
37
+ @aliases = aliases.map(&:to_s)
38
+ end
39
+
40
+ def self.summary(command)
41
+ if command.respond_to?(:description)
42
+ if (desc = command.description)
43
+ # extract the first sentence
44
+ desc[/^[^\.]+/]
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,141 @@
1
+ require 'command_kit/stdio'
2
+ require 'command_kit/env'
3
+
4
+ begin
5
+ require 'io/console'
6
+ rescue LoadError
7
+ end
8
+
9
+ module CommandKit
10
+ #
11
+ # Provides access to [IO.console] and [IO.console_size].
12
+ #
13
+ # ## Environment Variables
14
+ #
15
+ # * `LINES` - The explicit number of lines or rows the console should have.
16
+ # * `COLUMNS` - The explicit number of columns the console should have.
17
+ #
18
+ # [IO.console]: https://rubydoc.info/gems/io-console/IO#console-class_method
19
+ # [IO#winsize]: https://rubydoc.info/gems/io-console/IO#winsize-instance_method
20
+ #
21
+ module Console
22
+ include Stdio
23
+ include Env
24
+
25
+ # The default console height to fallback to.
26
+ DEFAULT_HEIGHT = 25
27
+
28
+ # The default console width to fallback to.
29
+ DEFAULT_WIDTH = 80
30
+
31
+ #
32
+ # Initializes any console settings.
33
+ #
34
+ # @param [Hash{Symbol => Object}] kwargs
35
+ # Additional keyword arguments.
36
+ #
37
+ # @note
38
+ # If the `$LINES` env variable is set, and is non-zero, it will be
39
+ # returned by {#console_height}.
40
+ #
41
+ # @note
42
+ # If the `$COLUMNS` env variable is set, and is non-zero, it will be
43
+ # returned by {#console_width}.
44
+ #
45
+ def initialize(**kwargs)
46
+ super(**kwargs)
47
+
48
+ @default_console_height = if (lines = env['LINES'])
49
+ lines.to_i
50
+ else
51
+ DEFAULT_HEIGHT
52
+ end
53
+
54
+ @default_console_width = if (columns = env['COLUMNS'])
55
+ columns.to_i
56
+ else
57
+ DEFAULT_WIDTH
58
+ end
59
+ end
60
+
61
+ #
62
+ # Determines if there is a console present.
63
+ #
64
+ # @return [Boolean]
65
+ # Specifies whether {Stdio#stdout stdout} is connected to a console.
66
+ #
67
+ def console?
68
+ IO.respond_to?(:console) && stdout.tty?
69
+ end
70
+
71
+ #
72
+ # Returns the console object, if {Stdio#stdout stdout} is connected to a
73
+ # console.
74
+ #
75
+ # @return [IO, nil]
76
+ # The IO objects or `nil` if {Stdio#stdout stdout} is not connected to a
77
+ # console.
78
+ #
79
+ # @example
80
+ # console
81
+ # # => #<File:/dev/tty>
82
+ #
83
+ def console
84
+ IO.console if console?
85
+ end
86
+
87
+ #
88
+ # Returns the console's height in number of lines.
89
+ #
90
+ # @return [Integer]
91
+ # The console's height in number of lines.
92
+ #
93
+ # @example
94
+ # console_height
95
+ # # => 22
96
+ #
97
+ def console_height
98
+ if (console = self.console)
99
+ console.winsize[0]
100
+ else
101
+ @default_console_height
102
+ end
103
+ end
104
+
105
+ #
106
+ # Returns the console's width in number of lines.
107
+ #
108
+ # @return [Integer]
109
+ # The console's width in number of columns.
110
+ #
111
+ # @example
112
+ # console_width
113
+ # # => 91
114
+ #
115
+ def console_width
116
+ if (console = self.console)
117
+ console.winsize[1]
118
+ else
119
+ @default_console_width
120
+ end
121
+ end
122
+
123
+ #
124
+ # The console height (lines) and width (columns).
125
+ #
126
+ # @return [(Integer, Integer)]
127
+ # Returns the height and width of the console.
128
+ #
129
+ # @example
130
+ # console_size
131
+ # # => [23, 91]
132
+ #
133
+ def console_size
134
+ if (console = self.console)
135
+ console.winsize
136
+ else
137
+ [@default_console_height, @default_console_width]
138
+ end
139
+ end
140
+ end
141
+ end