command_kit 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) 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 +29 -0
  5. data/Gemfile +3 -0
  6. data/README.md +141 -121
  7. data/Rakefile +3 -2
  8. data/command_kit.gemspec +4 -4
  9. data/examples/command.rb +1 -1
  10. data/gemspec.yml +7 -0
  11. data/lib/command_kit/arguments.rb +1 -1
  12. data/lib/command_kit/colors.rb +221 -45
  13. data/lib/command_kit/command.rb +1 -1
  14. data/lib/command_kit/commands.rb +4 -4
  15. data/lib/command_kit/help/man.rb +4 -25
  16. data/lib/command_kit/inflector.rb +47 -17
  17. data/lib/command_kit/main.rb +7 -9
  18. data/lib/command_kit/man.rb +44 -0
  19. data/lib/command_kit/open_app.rb +69 -0
  20. data/lib/command_kit/options/option.rb +1 -6
  21. data/lib/command_kit/options/parser.rb +15 -17
  22. data/lib/command_kit/options.rb +2 -2
  23. data/lib/command_kit/os/linux.rb +157 -0
  24. data/lib/command_kit/os.rb +159 -11
  25. data/lib/command_kit/package_manager.rb +200 -0
  26. data/lib/command_kit/pager.rb +46 -4
  27. data/lib/command_kit/printing/indent.rb +2 -2
  28. data/lib/command_kit/printing.rb +1 -1
  29. data/lib/command_kit/sudo.rb +40 -0
  30. data/lib/command_kit/terminal.rb +5 -0
  31. data/lib/command_kit/version.rb +1 -1
  32. data/spec/arguments/argument_spec.rb +1 -1
  33. data/spec/colors_spec.rb +256 -0
  34. data/spec/commands_spec.rb +1 -1
  35. data/spec/exception_handler_spec.rb +1 -1
  36. data/spec/help/man_spec.rb +0 -32
  37. data/spec/inflector_spec.rb +70 -8
  38. data/spec/man_spec.rb +46 -0
  39. data/spec/open_app_spec.rb +85 -0
  40. data/spec/options/option_spec.rb +2 -2
  41. data/spec/os/linux_spec.rb +154 -0
  42. data/spec/os_spec.rb +200 -13
  43. data/spec/package_manager_spec.rb +806 -0
  44. data/spec/pager_spec.rb +71 -6
  45. data/spec/sudo_spec.rb +51 -0
  46. data/spec/terminal_spec.rb +30 -0
  47. data/spec/usage_spec.rb +1 -1
  48. metadata +19 -4
@@ -4,19 +4,102 @@ module CommandKit
4
4
  #
5
5
  # ## Examples
6
6
  #
7
- # include CommandKit::OS
7
+ # require 'command_kit/command'
8
+ # require 'command_kit/os'
8
9
  #
9
- # def main(*argv)
10
- # if linux?
11
- # # ...
12
- # elsif macos?
13
- # # ...
14
- # elsif windows?
15
- # # ...
10
+ # class Command < CommandKit::Command
11
+ #
12
+ # include CommandKit::OS
13
+ #
14
+ # def main(*argv)
15
+ # if linux?
16
+ # # ...
17
+ # elsif macos?
18
+ # # ...
19
+ # elsif freebsd?
20
+ # # ...
21
+ # elsif windows?
22
+ # # ...
23
+ # end
16
24
  # end
25
+ #
17
26
  # end
18
27
  #
19
28
  module OS
29
+ #
30
+ # @api private
31
+ #
32
+ module ModuleMethods
33
+ #
34
+ # Extends {ClassMethods} or {ModuleMethods}, depending on whether
35
+ # {OS} is being included into a class or a module..
36
+ #
37
+ # @param [Class, Module] context
38
+ # The class or module which is including {OS}.
39
+ #
40
+ def included(context)
41
+ super(context)
42
+
43
+ if context.class == Module
44
+ context.extend ModuleMethods
45
+ else
46
+ context.extend ClassMethods
47
+ end
48
+ end
49
+ end
50
+
51
+ extend ModuleMethods
52
+
53
+ module ClassMethods
54
+ #
55
+ # Determines the current OS.
56
+ #
57
+ # @return [:linux, :macos, :freebsd, :openbsd, :netbsd, :windows, nil]
58
+ # The OS type or `nil` if the OS could not be determined.
59
+ #
60
+ # @api semipublic
61
+ #
62
+ # @since 0.2.0
63
+ #
64
+ def os
65
+ if RUBY_PLATFORM.include?('linux') then :linux
66
+ elsif RUBY_PLATFORM.include?('darwin') then :macos
67
+ elsif RUBY_PLATFORM.include?('freebsd') then :freebsd
68
+ elsif RUBY_PLATFORM.include?('openbsd') then :openbsd
69
+ elsif RUBY_PLATFORM.include?('netbsd') then :netbsd
70
+ elsif Gem.win_platform? then :windows
71
+ end
72
+ end
73
+ end
74
+
75
+ # The current OS.
76
+ #
77
+ # @return [:linux, :macos, :freebsd, :openbsd, :netbsd, :windows, nil]
78
+ #
79
+ # @api public
80
+ #
81
+ # @since 0.2.0
82
+ attr_reader :os
83
+
84
+ #
85
+ # Initializes the command.
86
+ #
87
+ # @param [:linux, :macos, :freebsd, :openbsd, :netbsd, :windows, nil] os
88
+ # Overrides the default OS.
89
+ #
90
+ # @param [Hash{Symbol => Object}] kwargs
91
+ # Additional keyword arguments.
92
+ #
93
+ # @api public
94
+ #
95
+ # @since 0.2.0
96
+ #
97
+ def initialize(os: self.class.os, **kwargs)
98
+ super(**kwargs)
99
+
100
+ @os = os
101
+ end
102
+
20
103
  #
21
104
  # Determines if the current OS is Linux.
22
105
  #
@@ -25,7 +108,7 @@ module CommandKit
25
108
  # @api public
26
109
  #
27
110
  def linux?
28
- RUBY_PLATFORM.include?('linux')
111
+ @os == :linux
29
112
  end
30
113
 
31
114
  #
@@ -36,7 +119,72 @@ module CommandKit
36
119
  # @api public
37
120
  #
38
121
  def macos?
39
- RUBY_PLATFORM.include?('darwin')
122
+ @os == :macos
123
+ end
124
+
125
+ #
126
+ # Determines if the current OS is FreeBSD.
127
+ #
128
+ # @return [Boolean]
129
+ #
130
+ # @api public
131
+ #
132
+ # @since 0.2.0
133
+ #
134
+ def freebsd?
135
+ @os == :freebsd
136
+ end
137
+
138
+ #
139
+ # Determines if the current OS is OpenBSD.
140
+ #
141
+ # @return [Boolean]
142
+ #
143
+ # @api public
144
+ #
145
+ # @since 0.2.0
146
+ #
147
+ def openbsd?
148
+ @os == :openbsd
149
+ end
150
+
151
+ #
152
+ # Determines if the current OS is NetBSD.
153
+ #
154
+ # @return [Boolean]
155
+ #
156
+ # @api public
157
+ #
158
+ # @since 0.2.0
159
+ #
160
+ def netbsd?
161
+ @os == :netbsd
162
+ end
163
+
164
+ #
165
+ # Determines if the current OS is BSD based.
166
+ #
167
+ # @return [Boolean]
168
+ #
169
+ # @since 0.2.0
170
+ #
171
+ # @api public
172
+ #
173
+ def bsd?
174
+ freebsd? || openbsd? || netbsd?
175
+ end
176
+
177
+ #
178
+ # Determines if the current OS is UNIX based.
179
+ #
180
+ # @return [Boolean]
181
+ #
182
+ # @since 0.2.0
183
+ #
184
+ # @api public
185
+ #
186
+ def unix?
187
+ linux? || macos? || bsd?
40
188
  end
41
189
 
42
190
  #
@@ -47,7 +195,7 @@ module CommandKit
47
195
  # @api public
48
196
  #
49
197
  def windows?
50
- Gem.win_platform?
198
+ @os == :windows
51
199
  end
52
200
  end
53
201
  end
@@ -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
@@ -5,6 +5,8 @@ require 'command_kit/env/path'
5
5
  require 'command_kit/stdio'
6
6
  require 'command_kit/terminal'
7
7
 
8
+ require 'shellwords'
9
+
8
10
  module CommandKit
9
11
  #
10
12
  # Allows opening a pager, such as `less` or `more`.
@@ -50,7 +52,7 @@ module CommandKit
50
52
  # Keyword arguments.
51
53
  #
52
54
  # @note
53
- # Respects the `PAGER` env variable, or attemps to find `less` or
55
+ # Respects the `PAGER` env variable, or attempts to find `less` or
54
56
  # `more` by searching the `PATH` env variable.
55
57
  #
56
58
  # @api public
@@ -58,7 +60,7 @@ module CommandKit
58
60
  def initialize(**kwargs)
59
61
  super(**kwargs)
60
62
 
61
- @pager = env.fetch('PAGER') do
63
+ @pager_command = env.fetch('PAGER') do
62
64
  PAGERS.find do |command|
63
65
  bin = command.split(' ',2).first
64
66
 
@@ -94,14 +96,14 @@ module CommandKit
94
96
  # @api public
95
97
  #
96
98
  def pager
97
- if !stdout.tty? || @pager.nil?
99
+ if !stdout.tty? || @pager_command.nil?
98
100
  # fallback to stdout if the process does not have a terminal or we could
99
101
  # not find a suitable pager command.
100
102
  yield stdout
101
103
  return
102
104
  end
103
105
 
104
- io = IO.popen(@pager,'w')
106
+ io = IO.popen(@pager_command,'w')
105
107
  pid = io.pid
106
108
 
107
109
  begin
@@ -144,5 +146,45 @@ module CommandKit
144
146
  stdout.puts(data)
145
147
  end
146
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
147
189
  end
148
190
  end