command_kit 0.1.0.pre1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +15 -0
  3. data/.rubocop.yml +138 -0
  4. data/ChangeLog.md +34 -2
  5. data/Gemfile +3 -0
  6. data/README.md +135 -214
  7. data/Rakefile +3 -2
  8. data/command_kit.gemspec +4 -4
  9. data/examples/colors.rb +30 -0
  10. data/examples/command.rb +65 -0
  11. data/examples/pager.rb +30 -0
  12. data/gemspec.yml +10 -2
  13. data/lib/command_kit/arguments/argument.rb +16 -44
  14. data/lib/command_kit/arguments/argument_value.rb +3 -30
  15. data/lib/command_kit/arguments.rb +66 -20
  16. data/lib/command_kit/colors.rb +253 -45
  17. data/lib/command_kit/command.rb +50 -3
  18. data/lib/command_kit/command_name.rb +9 -0
  19. data/lib/command_kit/commands/auto_load/subcommand.rb +3 -0
  20. data/lib/command_kit/commands/auto_load.rb +16 -0
  21. data/lib/command_kit/commands/auto_require.rb +16 -0
  22. data/lib/command_kit/commands/command.rb +3 -0
  23. data/lib/command_kit/commands/help.rb +2 -0
  24. data/lib/command_kit/commands/parent_command.rb +7 -0
  25. data/lib/command_kit/commands/subcommand.rb +15 -0
  26. data/lib/command_kit/commands.rb +40 -4
  27. data/lib/command_kit/description.rb +15 -2
  28. data/lib/command_kit/env/home.rb +9 -0
  29. data/lib/command_kit/env/path.rb +15 -0
  30. data/lib/command_kit/env.rb +4 -0
  31. data/lib/command_kit/examples.rb +15 -2
  32. data/lib/command_kit/exception_handler.rb +4 -0
  33. data/lib/command_kit/help/man.rb +74 -47
  34. data/lib/command_kit/help.rb +10 -1
  35. data/lib/command_kit/inflector.rb +49 -17
  36. data/lib/command_kit/interactive.rb +239 -0
  37. data/lib/command_kit/main.rb +20 -9
  38. data/lib/command_kit/man.rb +44 -0
  39. data/lib/command_kit/open_app.rb +69 -0
  40. data/lib/command_kit/options/option.rb +36 -9
  41. data/lib/command_kit/options/option_value.rb +42 -3
  42. data/lib/command_kit/options/parser.rb +44 -17
  43. data/lib/command_kit/options/quiet.rb +3 -0
  44. data/lib/command_kit/options/verbose.rb +5 -0
  45. data/lib/command_kit/options/version.rb +6 -0
  46. data/lib/command_kit/options.rb +59 -10
  47. data/lib/command_kit/os/linux.rb +157 -0
  48. data/lib/command_kit/os.rb +165 -11
  49. data/lib/command_kit/package_manager.rb +200 -0
  50. data/lib/command_kit/pager.rb +84 -9
  51. data/lib/command_kit/printing/indent.rb +25 -2
  52. data/lib/command_kit/printing.rb +23 -0
  53. data/lib/command_kit/program_name.rb +7 -0
  54. data/lib/command_kit/stdio.rb +24 -0
  55. data/lib/command_kit/sudo.rb +40 -0
  56. data/lib/command_kit/terminal.rb +159 -0
  57. data/lib/command_kit/usage.rb +14 -0
  58. data/lib/command_kit/version.rb +1 -1
  59. data/lib/command_kit/xdg.rb +21 -1
  60. data/lib/command_kit.rb +1 -0
  61. data/spec/arguments/argument_spec.rb +5 -41
  62. data/spec/arguments/argument_value_spec.rb +1 -61
  63. data/spec/arguments_spec.rb +8 -25
  64. data/spec/colors_spec.rb +277 -13
  65. data/spec/command_name_spec.rb +1 -1
  66. data/spec/command_spec.rb +4 -1
  67. data/spec/commands/auto_load/subcommand_spec.rb +1 -1
  68. data/spec/commands/auto_load_spec.rb +1 -1
  69. data/spec/commands/auto_require_spec.rb +2 -2
  70. data/spec/commands/help_spec.rb +1 -1
  71. data/spec/commands/parent_command_spec.rb +1 -1
  72. data/spec/commands/subcommand_spec.rb +1 -1
  73. data/spec/commands_spec.rb +2 -2
  74. data/spec/description_spec.rb +1 -25
  75. data/spec/env/home_spec.rb +1 -1
  76. data/spec/env/path_spec.rb +1 -1
  77. data/spec/examples_spec.rb +1 -25
  78. data/spec/exception_handler_spec.rb +1 -1
  79. data/spec/help/man_spec.rb +316 -0
  80. data/spec/help_spec.rb +0 -25
  81. data/spec/inflector_spec.rb +71 -9
  82. data/spec/interactive_spec.rb +415 -0
  83. data/spec/main_spec.rb +7 -7
  84. data/spec/man_spec.rb +46 -0
  85. data/spec/open_app_spec.rb +85 -0
  86. data/spec/options/option_spec.rb +48 -9
  87. data/spec/options/option_value_spec.rb +53 -4
  88. data/spec/options_spec.rb +1 -1
  89. data/spec/os/linux_spec.rb +154 -0
  90. data/spec/os_spec.rb +201 -14
  91. data/spec/package_manager_spec.rb +806 -0
  92. data/spec/pager_spec.rb +78 -15
  93. data/spec/printing/indent_spec.rb +1 -1
  94. data/spec/printing_spec.rb +10 -2
  95. data/spec/program_name_spec.rb +1 -1
  96. data/spec/spec_helper.rb +0 -3
  97. data/spec/sudo_spec.rb +51 -0
  98. data/spec/{console_spec.rb → terminal_spec.rb} +65 -35
  99. data/spec/usage_spec.rb +2 -2
  100. data/spec/xdg_spec.rb +1 -1
  101. metadata +32 -13
  102. data/lib/command_kit/arguments/usage.rb +0 -6
  103. data/lib/command_kit/console.rb +0 -141
  104. data/lib/command_kit/options/usage.rb +0 -6
@@ -0,0 +1,200 @@
1
+ require 'command_kit/os'
2
+ require 'command_kit/os/linux'
3
+ require 'command_kit/env/path'
4
+ require 'command_kit/sudo'
5
+
6
+ module CommandKit
7
+ #
8
+ # Allows installing packages using the system's package manager.
9
+ #
10
+ # Supports the following package managers:
11
+ #
12
+ # * Linux
13
+ # * Debian / Ubuntu
14
+ # * `apt`
15
+ # * RedHat / Fedora
16
+ # * `dnf`
17
+ # * `yum`
18
+ # * Arch
19
+ # * `pacman`
20
+ # * SUSE / OpenSUSE
21
+ # * `zypper`
22
+ # * macOS
23
+ # * `brew`
24
+ # * `port`
25
+ # * FreeBSD
26
+ # * `pkg`
27
+ # * OpenBSD
28
+ # * `pkg_add`
29
+ #
30
+ # ## Examples
31
+ #
32
+ # unless install_packages("nmap")
33
+ # print_error "failed to install nmap"
34
+ # exit -1
35
+ # end
36
+ #
37
+ # ### Installing multiple packages
38
+ #
39
+ # install_packages apt: ["libxml2-dev", ...],
40
+ # dnf: ["libxml2-devel", ...],
41
+ # brew: ["libxml2", ...],
42
+ # ...
43
+ #
44
+ # @since 0.2.0
45
+ #
46
+ module PackageManager
47
+ include OS
48
+ include OS::Linux
49
+ include Env::Path
50
+ include Sudo
51
+
52
+ # The detected package manager.
53
+ #
54
+ # @return [:apt, :dnf, :yum, :zypper, :pacman, :brew, :pkg, :pkg_add, nil]
55
+ attr_reader :package_manager
56
+
57
+ #
58
+ # Initializes the command and determines which open command to use.
59
+ #
60
+ # @param [:apt, :dnf, :yum, :zypper, :pacman, :brew, :pkg, :pkg_add, nil] package_manager
61
+ # The explicit package manager to use. If `nil`, the package manager will
62
+ # be detected.
63
+ #
64
+ def initialize(package_manager: nil, **kwargs)
65
+ super(**kwargs)
66
+
67
+ @package_manager = package_manager || begin
68
+ if macos?
69
+ if command_installed?('brew') then :brew
70
+ elsif command_installed?('port') then :port
71
+ end
72
+ elsif linux?
73
+ if redhat_linux?
74
+ if command_installed?('dnf') then :dnf
75
+ elsif command_installed?('yum') then :yum
76
+ end
77
+ elsif debian_linux?
78
+ :apt if command_installed?('apt')
79
+ elsif suse_linux?
80
+ :zypper if command_installed?('zypper')
81
+ elsif arch_linux?
82
+ :pacman if command_installed?('pacman')
83
+ end
84
+ elsif freebsd?
85
+ :pkg if command_installed?('pkg')
86
+ elsif openbsd?
87
+ :pkg_add if command_installed?('pkg_add')
88
+ end
89
+ end
90
+ end
91
+
92
+ #
93
+ # Installs the packages using the system's package manager.
94
+ #
95
+ # @param [Array<String>, String] packages
96
+ # A list of package name(s) to install.
97
+ #
98
+ # @param [Boolean] yes
99
+ # Assume yes for all user prompts.
100
+ #
101
+ # @param [Array<String>, String] apt
102
+ # List of `apt` specific package names.
103
+ #
104
+ # @param [Array<String>, String] brew
105
+ # List of `brew` specific package names.
106
+ #
107
+ # @param [Array<String>, String] dnf
108
+ # List of `dnf` specific package names.
109
+ #
110
+ # @param [Array<String>, String] pacman
111
+ # List of `pacman` specific package names.
112
+ #
113
+ # @param [Array<String>, String] pkg
114
+ # List of `pkg` specific package names.
115
+ #
116
+ # @param [Array<String>, String] pkg_add
117
+ # List of `pkg_add` specific package names.
118
+ #
119
+ # @param [Array<String>, String] port
120
+ # List of `port` specific package names.
121
+ #
122
+ # @param [Array<String>, String] yum
123
+ # List of `yum` specific package names.
124
+ #
125
+ # @param [Array<String>, String] zypper
126
+ # List of `zypper` specific package names.
127
+ #
128
+ # @return [Boolean, nil]
129
+ # Specifies whether the packages were successfully installed or not.
130
+ # If the package manager command could not be determined, `nil` is
131
+ # returned.
132
+ #
133
+ # @example Install a package
134
+ # install_packages "nmap", ...
135
+ #
136
+ # @example Install a list of packages per package-manager
137
+ # install_packages apt: ["libxml2-dev", ...],
138
+ # dnf: ["libxml2-devel", ...],
139
+ # brew: ["libxml2", ...],
140
+ # ...
141
+ #
142
+ def install_packages(*packages, yes: false,
143
+ apt: nil,
144
+ brew: nil,
145
+ dnf: nil,
146
+ pacman: nil,
147
+ pkg: nil,
148
+ pkg_add: nil,
149
+ port: nil,
150
+ yum: nil,
151
+ zypper: nil)
152
+ specific_package_names = case @package_manager
153
+ when :apt then apt
154
+ when :brew then brew
155
+ when :dnf then dnf
156
+ when :pacman then pacman
157
+ when :pkg then pkg
158
+ when :pkg_add then pkg_add
159
+ when :port then port
160
+ when :yum then yum
161
+ when :zypper then zypper
162
+ end
163
+ packages += Array(specific_package_names)
164
+
165
+ case @package_manager
166
+ when :apt
167
+ args = []
168
+ args << '-y' if yes
169
+
170
+ sudo('apt','install',*args,*packages)
171
+ when :brew
172
+ system('brew','install',*packages)
173
+ when :dnf, :yum
174
+ args = []
175
+ args << '-y' if yes
176
+
177
+ sudo(@package_manager.to_s,'install',*args,*packages)
178
+ when :pacman
179
+ missing_packages = `pacman -T #{Shellwords.shelljoin(packages)}`.split
180
+
181
+ if missing_packages.empty?
182
+ return true
183
+ end
184
+
185
+ sudo('pacman','-S',*missing_packages)
186
+ when :pkg
187
+ args = []
188
+ args << '-y' if yes
189
+
190
+ sudo('pkg','install',*args,*packages)
191
+ when :pkg_add
192
+ sudo('pkg_add',*packages)
193
+ when :port
194
+ sudo('port','install',*packages)
195
+ when :zypper
196
+ sudo('zypper','-n','in','-l',*packages)
197
+ end
198
+ end
199
+ end
200
+ end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'command_kit/stdio'
4
3
  require 'command_kit/env'
5
4
  require 'command_kit/env/path'
5
+ require 'command_kit/stdio'
6
+ require 'command_kit/terminal'
7
+
8
+ require 'shellwords'
6
9
 
7
10
  module CommandKit
8
11
  #
@@ -16,6 +19,7 @@ module CommandKit
16
19
  #
17
20
  # def run
18
21
  # pager do |io|
22
+ # io.puts "This goes into the pager screen"
19
23
  # end
20
24
  # end
21
25
  #
@@ -33,9 +37,10 @@ module CommandKit
33
37
  # * [tty-pager](https://github.com/piotrmurach/tty-pager#readme)
34
38
  #
35
39
  module Pager
36
- include Stdio
37
40
  include Env
38
41
  include Env::Path
42
+ include Stdio
43
+ include Terminal
39
44
 
40
45
  # Common pager commands.
41
46
  PAGERS = ['less -r', 'more -r']
@@ -43,14 +48,19 @@ module CommandKit
43
48
  #
44
49
  # Initializes the pager.
45
50
  #
51
+ # @param [Hash{Symbol => Object}] kwargs
52
+ # Keyword arguments.
53
+ #
46
54
  # @note
47
- # Respects the `PAGER` env variable, or attemps to find `less` or
55
+ # Respects the `PAGER` env variable, or attempts to find `less` or
48
56
  # `more` by searching the `PATH` env variable.
49
57
  #
58
+ # @api public
59
+ #
50
60
  def initialize(**kwargs)
51
61
  super(**kwargs)
52
62
 
53
- @pager = env.fetch('PAGER') do
63
+ @pager_command = env.fetch('PAGER') do
54
64
  PAGERS.find do |command|
55
65
  bin = command.split(' ',2).first
56
66
 
@@ -68,15 +78,32 @@ module CommandKit
68
78
  # @yieldparam [IO]
69
79
  # The IO pipe to the pager.
70
80
  #
81
+ # @example
82
+ # pager do |io|
83
+ # io.puts "Hello world"
84
+ # # ...
85
+ # end
86
+ #
87
+ # @example Piping a command into the pager:
88
+ # IO.peopn(["ping", ip]) do |ping|
89
+ # pager do |io|
90
+ # ping.each_line do |line|
91
+ # io.write line
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ # @api public
97
+ #
71
98
  def pager
72
- if !stdout.tty? || @pager.nil?
73
- # fallback to stdout if the process does not have a console or we could
99
+ if !stdout.tty? || @pager_command.nil?
100
+ # fallback to stdout if the process does not have a terminal or we could
74
101
  # not find a suitable pager command.
75
102
  yield stdout
76
103
  return
77
104
  end
78
105
 
79
- io = IO.popen(@pager,'w')
106
+ io = IO.popen(@pager_command,'w')
80
107
  pid = io.pid
81
108
 
82
109
  begin
@@ -93,23 +120,71 @@ module CommandKit
93
120
  end
94
121
 
95
122
  #
96
- # Pages the data if it's longer the console's height, otherwise prints the
123
+ # Pages the data if it's longer the terminal's height, otherwise prints the
97
124
  # data to {Stdio#stdout stdout}.
98
125
  #
99
126
  # @param [Array<String>, #to_s] data
100
127
  # The data to print.
101
128
  #
129
+ # @example
130
+ # print_or_page(data)
131
+ #
132
+ # @example Print or pages the contents of a file:
133
+ # print_or_page(File.read(file))
134
+ #
135
+ # @api public
136
+ #
102
137
  def print_or_page(data)
103
138
  line_count = case data
104
139
  when Array then data.length
105
140
  else data.to_s.each_line.count
106
141
  end
107
142
 
108
- if line_count > console_height
143
+ if line_count > terminal_height
109
144
  pager { |io| io.puts(data) }
110
145
  else
111
146
  stdout.puts(data)
112
147
  end
113
148
  end
149
+
150
+ #
151
+ # Pipes a command into the pager.
152
+ #
153
+ # @param [#to_s] command
154
+ # The program or command to run.
155
+ #
156
+ # @param [Array<#to_s>] arguments
157
+ # Additional arguments for the program.
158
+ #
159
+ # @return [Boolean]
160
+ # Indicates whether the command exited successfully or not.
161
+ #
162
+ # @note
163
+ # If multiple arguments are given, they will be shell-escaped and executed
164
+ # as a single command.
165
+ # If a single command is given, it will not be shell-escaped to allow
166
+ # executing compound shell commands.
167
+ #
168
+ # @example Pipe a single command into the pager:
169
+ # run_in_pager 'find', '.', '-name', '*.md'
170
+ #
171
+ # @example Pipe a compound command into the pager:
172
+ # run_in_pager "wc -l /path/to/wordlists/*.txt | sort -n"
173
+ #
174
+ # @api public
175
+ #
176
+ # @since 0.2.0
177
+ #
178
+ def pipe_to_pager(command,*arguments)
179
+ if @pager_command
180
+ unless arguments.empty?
181
+ command = Shellwords.shelljoin([command, *arguments])
182
+ end
183
+
184
+ system("#{command} | #{@pager_command}")
185
+ else
186
+ system(command.to_s,*arguments.map(&:to_s))
187
+ end
188
+ end
114
189
  end
115
190
  end
@@ -20,7 +20,7 @@ module CommandKit
20
20
  #
21
21
  module Indent
22
22
  #
23
- # Initializes the indententation level to zero.
23
+ # Initializes the indentation level to zero.
24
24
  #
25
25
  def initialize(**kwargs)
26
26
  @indent = 0
@@ -40,7 +40,28 @@ module CommandKit
40
40
  # increased.
41
41
  #
42
42
  # @return [Integer]
43
- # If no block is given, the indententation level will be returned.
43
+ # If no block is given, the indentation level will be returned.
44
+ #
45
+ # @example
46
+ # puts "values:"
47
+ # indent do
48
+ # values.each do |key,value|
49
+ # puts "#{key}: #{value}"
50
+ # end
51
+ # end
52
+ #
53
+ # @example
54
+ # puts "Code:"
55
+ # puts
56
+ # puts "```"
57
+ # indent(4) do
58
+ # code.each_line do |line|
59
+ # puts line
60
+ # end
61
+ # end
62
+ # puts "```"
63
+ #
64
+ # @api public
44
65
  #
45
66
  def indent(n=2)
46
67
  if block_given?
@@ -63,6 +84,8 @@ module CommandKit
63
84
  # @param [Array<String>] lines
64
85
  # The lines to indent and print.
65
86
  #
87
+ # @api public
88
+ #
66
89
  def puts(*lines)
67
90
  if (@indent > 0 && !lines.empty?)
68
91
  padding = " " * @indent
@@ -9,12 +9,24 @@ module CommandKit
9
9
  module Printing
10
10
  include Stdio
11
11
 
12
+ # Platform independent new-line constant
13
+ #
14
+ # @return [String]
15
+ #
16
+ # @api public
17
+ EOL = $/
18
+
12
19
  #
13
20
  # Prints the error message to {Stdio#stderr stderr}.
14
21
  #
15
22
  # @param [String] message
16
23
  # The error message.
17
24
  #
25
+ # @example
26
+ # print_error "Error: invalid input"
27
+ #
28
+ # @api public
29
+ #
18
30
  def print_error(message)
19
31
  stderr.puts message
20
32
  end
@@ -25,6 +37,17 @@ module CommandKit
25
37
  # @param [Exception] error
26
38
  # The error to print.
27
39
  #
40
+ # @example
41
+ # begin
42
+ # # ...
43
+ # rescue => error
44
+ # print_error "Error encountered"
45
+ # print_exception(error)
46
+ # exit(1)
47
+ # end
48
+ #
49
+ # @api public
50
+ #
28
51
  def print_exception(error)
29
52
  print_error error.full_message(highlight: stderr.tty?)
30
53
  end