command_kit 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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