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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'strscan'
4
+
3
5
  module CommandKit
4
6
  #
5
7
  # A very simple inflector.
@@ -33,16 +35,37 @@ module CommandKit
33
35
  # @return [String]
34
36
  # The resulting under_scored name.
35
37
  #
38
+ # @raise [ArgumentError]
39
+ # The given string contained non-alpha-numeric characters.
40
+ #
36
41
  def self.underscore(name)
37
- # sourced from: https://github.com/dry-rb/dry-inflector/blob/c918f967ff82611da374eb0847a77b7e012d3fa8/lib/dry/inflector.rb#L286-L287
38
- name = name.to_s.dup
42
+ scanner = StringScanner.new(name.to_s)
43
+ new_string = String.new
44
+
45
+ until scanner.eos?
46
+ if (separator = scanner.scan(/[_-]+/))
47
+ new_string << '_' * separator.length
48
+ else
49
+ if (capitalized = scanner.scan(/[A-Z][a-z\d]+/))
50
+ new_string << capitalized
51
+ elsif (uppercase = scanner.scan(/[A-Z][A-Z\d]*(?=[A-Z_-]|$)/))
52
+ new_string << uppercase
53
+ elsif (lowercase = scanner.scan(/[a-z][a-z\d]*/))
54
+ new_string << lowercase
55
+ else
56
+ raise(ArgumentError,"cannot convert string to underscored: #{scanner.string.inspect}")
57
+ end
39
58
 
40
- name.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
41
- name.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
42
- name.tr!('-','_')
43
- name.downcase!
59
+ if (separator = scanner.scan(/[_-]+/))
60
+ new_string << '_' * separator.length
61
+ elsif !scanner.eos?
62
+ new_string << '_'
63
+ end
64
+ end
65
+ end
44
66
 
45
- name
67
+ new_string.downcase!
68
+ new_string
46
69
  end
47
70
 
48
71
  #
@@ -67,20 +90,27 @@ module CommandKit
67
90
  # @return [String]
68
91
  # The CamelCased name.
69
92
  #
93
+ # @raise [ArgumentError]
94
+ # The given under_scored string contained non-alpha-numeric characters.
95
+ #
70
96
  def self.camelize(name)
71
- name = name.to_s.dup
72
-
73
- # sourced from: https://github.com/dry-rb/dry-inflector/blob/c918f967ff82611da374eb0847a77b7e012d3fa8/lib/dry/inflector.rb#L329-L334
74
- name.sub!(/^[a-z\d]*/,&:capitalize)
75
- name.gsub!(%r{(?:[_-]|(/))([a-z\d]*)}i) do |match|
76
- slash = Regexp.last_match(1)
77
- word = Regexp.last_match(2)
97
+ scanner = StringScanner.new(name.to_s)
98
+ new_string = String.new
78
99
 
79
- "#{slash}#{word.capitalize}"
100
+ until scanner.eos?
101
+ if (word = scanner.scan(/[A-Za-z\d]+/))
102
+ word.capitalize!
103
+ new_string << word
104
+ elsif scanner.scan(/[_-]+/)
105
+ # skip
106
+ elsif scanner.scan(/\//)
107
+ new_string << '::'
108
+ else
109
+ raise(ArgumentError,"cannot convert string to CamelCase: #{scanner.string.inspect}")
110
+ end
80
111
  end
81
112
 
82
- name.gsub!('/','::')
83
- name
113
+ new_string
84
114
  end
85
115
  end
86
116
  end
@@ -49,15 +49,13 @@ module CommandKit
49
49
  # @api public
50
50
  #
51
51
  def start(argv=ARGV, **kwargs)
52
- begin
53
- exit main(argv, **kwargs)
54
- rescue Interrupt
55
- # https://tldp.org/LDP/abs/html/exitcodes.html
56
- exit 130
57
- rescue Errno::EPIPE
58
- # STDOUT pipe broken
59
- exit 0
60
- end
52
+ exit main(argv, **kwargs)
53
+ rescue Interrupt
54
+ # https://tldp.org/LDP/abs/html/exitcodes.html
55
+ exit 130
56
+ rescue Errno::EPIPE
57
+ # STDOUT pipe broken
58
+ exit 0
61
59
  end
62
60
 
63
61
  #
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandKit
4
+ #
5
+ # Allows displaying man pages.
6
+ #
7
+ # ## Examples
8
+ #
9
+ # man "passwd"
10
+ # man "passwd", section: 5
11
+ #
12
+ # @since 0.2.0
13
+ #
14
+ module Man
15
+ #
16
+ # Displays the given man page.
17
+ #
18
+ # @param [String] page
19
+ # The man page file name.
20
+ #
21
+ # @param [Integer, String, nil] section
22
+ # The optional section number to specify.
23
+ #
24
+ # @return [Boolean, nil]
25
+ # Specifies whether the `man` command was successful or not.
26
+ # Returns `nil` when the `man` command is not installed.
27
+ #
28
+ # @example
29
+ # man "passwd"
30
+ #
31
+ # @example Display a man-page from a specific section:
32
+ # man "passwd", section: 5
33
+ #
34
+ # @api public
35
+ #
36
+ def man(page, section: nil)
37
+ if section
38
+ system('man',section.to_s,page.to_s)
39
+ else
40
+ system('man',page.to_s)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,69 @@
1
+ require 'command_kit/os'
2
+ require 'command_kit/env/path'
3
+
4
+ module CommandKit
5
+ #
6
+ # Allows opening a file or a URI with the system's preferred application for
7
+ # that file type or URI scheme.
8
+ #
9
+ # ## Examples
10
+ #
11
+ # open_app_for "movie.avi"
12
+ # open_app_for "https://github.com/postmodern/command_kit.rb"
13
+ #
14
+ # @since 0.2.0
15
+ #
16
+ module OpenApp
17
+ include OS
18
+ include Env::Path
19
+
20
+ #
21
+ # Initializes the command and determines which open command to use.
22
+ #
23
+ # @param [Hash{Symbol => Object}] kwargs
24
+ # Additional keyword arguments.
25
+ #
26
+ # @api public
27
+ #
28
+ def initialize(**kwargs)
29
+ super(**kwargs)
30
+
31
+ @open_command = if macos?
32
+ 'open'
33
+ elsif linux? || bsd?
34
+ if command_installed?('xdg-open')
35
+ 'xdg-open'
36
+ end
37
+ elsif windows?
38
+ if command_installed?('invoke-item')
39
+ 'invoke-item'
40
+ else
41
+ 'start'
42
+ end
43
+ end
44
+ end
45
+
46
+ #
47
+ # Opens a file or URI using the system's preferred application for that
48
+ # file type or URI scheme.
49
+ #
50
+ # @param [String, URI] file_or_uri
51
+ # The file path or URI to open.
52
+ #
53
+ # @return [Boolean, nil]
54
+ # Specifies whether the file or URI was successfully opened or not.
55
+ # If the open command could not be determined, `nil` is returned.
56
+ #
57
+ # @example Open a file:
58
+ # open_app_for "movie.avi"
59
+ #
60
+ # @example Open a URI:
61
+ # open_app_for "https://github.com/postmodern/command_kit.rb"
62
+ #
63
+ def open_app_for(file_or_uri)
64
+ if @open_command
65
+ system(@open_command,file_or_uri.to_s)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -35,11 +35,6 @@ module CommandKit
35
35
  # @return [OptionValue, nil]
36
36
  attr_reader :value
37
37
 
38
- # The option's description.
39
- #
40
- # @return [String]
41
- attr_reader :desc
42
-
43
38
  # The optional block that will receive the parsed option value.
44
39
  #
45
40
  # @return [Proc, nil]
@@ -128,7 +123,7 @@ module CommandKit
128
123
  end
129
124
 
130
125
  #
131
- # The separator characer between the option and option value.
126
+ # The separator character between the option and option value.
132
127
  #
133
128
  # @return ['=', ' ', nil]
134
129
  #
@@ -118,23 +118,21 @@ module CommandKit
118
118
  # @api semipublic
119
119
  #
120
120
  def parse_options(argv)
121
- begin
122
- option_parser.parse(argv)
123
- rescue OptionParser::InvalidOption => error
124
- on_invalid_option(error)
125
- rescue OptionParser::AmbiguousOption => error
126
- on_ambiguous_option(error)
127
- rescue OptionParser::InvalidArgument => error
128
- on_invalid_argument(error)
129
- rescue OptionParser::MissingArgument => error
130
- on_missing_argument(error)
131
- rescue OptionParser::NeedlessArgument => error
132
- on_needless_argument(error)
133
- rescue OptionParser::AmbiguousArgument => error
134
- on_ambiguous_argument(error)
135
- rescue OptionParser::ParseError => error
136
- on_parse_error(error)
137
- end
121
+ option_parser.parse(argv)
122
+ rescue OptionParser::InvalidOption => error
123
+ on_invalid_option(error)
124
+ rescue OptionParser::AmbiguousOption => error
125
+ on_ambiguous_option(error)
126
+ rescue OptionParser::InvalidArgument => error
127
+ on_invalid_argument(error)
128
+ rescue OptionParser::MissingArgument => error
129
+ on_missing_argument(error)
130
+ rescue OptionParser::NeedlessArgument => error
131
+ on_needless_argument(error)
132
+ rescue OptionParser::AmbiguousArgument => error
133
+ on_ambiguous_argument(error)
134
+ rescue OptionParser::ParseError => error
135
+ on_parse_error(error)
138
136
  end
139
137
 
140
138
  #
@@ -22,7 +22,7 @@ module CommandKit
22
22
  # @bar = arg.split(':')
23
23
  # end
24
24
  #
25
- # ### initialize and using ivars
25
+ # ### initialize and using instance variables
26
26
  #
27
27
  # option :number, value: {type: Integer},
28
28
  # desc: 'Numbers' do |num|
@@ -197,7 +197,7 @@ module CommandKit
197
197
  # {Parser#option_parser option parser}.
198
198
  #
199
199
  # @param [Hash{Symbol => Object}] options
200
- # Optional pre-populated options hash.
200
+ # Optional prepopulated options hash.
201
201
  #
202
202
  # @note
203
203
  # The {#option_parser} will populate {#options} and also call any
@@ -0,0 +1,157 @@
1
+ require 'command_kit/os'
2
+
3
+ module CommandKit
4
+ module OS
5
+ #
6
+ # Provides methods for determining the specific type of Linux.
7
+ #
8
+ # ## Example
9
+ #
10
+ # require 'command_kit/command'
11
+ # require 'command_kit/os/linux'
12
+ #
13
+ # class Command < CommandKit::Command
14
+ #
15
+ # include CommandKit::OS::Linux
16
+ #
17
+ # def run
18
+ # if debian_linux?
19
+ # # ...
20
+ # elsif redhat_linux?
21
+ # # ...
22
+ # elsif suse_linux?
23
+ # # ...
24
+ # elsif arch_linux?
25
+ # # ...
26
+ # end
27
+ # end
28
+ # end
29
+ #
30
+ # @since 0.2.0
31
+ #
32
+ module Linux
33
+ #
34
+ # @api private
35
+ #
36
+ module ModuleMethods
37
+ #
38
+ # Extends {ClassMethods} or {ModuleMethods}, depending on whether
39
+ # {OS} is being included into a class or a module..
40
+ #
41
+ # @param [Class, Module] context
42
+ # The class or module which is including {OS}.
43
+ #
44
+ def included(context)
45
+ super(context)
46
+
47
+ if context.class == Module
48
+ context.extend ModuleMethods
49
+ else
50
+ context.extend ClassMethods
51
+ end
52
+ end
53
+ end
54
+
55
+ extend ModuleMethods
56
+
57
+ module ClassMethods
58
+ #
59
+ # Determines the specific Linux distro.
60
+ #
61
+ # @return [:fedora, :redhat, :debian, :suse, :arch, nil]
62
+ # Returns the type of Linux distro or `nil` if the Linux distro could
63
+ # not be determined.
64
+ #
65
+ # @api semipublic
66
+ #
67
+ def linux_distro
68
+ if File.file?('/etc/fedora-release') then :fedora
69
+ elsif File.file?('/etc/redhat-release') then :redhat
70
+ elsif File.file?('/etc/debian_version') then :debian
71
+ elsif File.file?('/etc/SuSE-release') then :suse
72
+ elsif File.file?('/etc/arch-release') then :arch
73
+ end
74
+ end
75
+ end
76
+
77
+ # The Linux distro.
78
+ #
79
+ # @return [:fedora, :redhat, :debian, :suse, :arch, nil]
80
+ #
81
+ # @api public
82
+ attr_reader :linux_distro
83
+
84
+ #
85
+ # Initializes the command.
86
+ #
87
+ # @param [:fedora, :redhat, :debian, :suse, :arch, nil] linux_distro
88
+ # Overrides the default detected Linux distro.
89
+ #
90
+ # @param [Hash{Symbol => Object}] kwargs
91
+ # Additional keyword arguments.
92
+ #
93
+ # @api public
94
+ #
95
+ def initialize(linux_distro: self.class.linux_distro, **kwargs)
96
+ super(**kwargs)
97
+
98
+ @linux_distro = linux_distro
99
+ end
100
+
101
+ #
102
+ # Determines if the current OS is RedHat Linux based distro.
103
+ #
104
+ # @return [Boolean]
105
+ #
106
+ # @api public
107
+ #
108
+ def redhat_linux?
109
+ @linux_distro == :redhat
110
+ end
111
+
112
+ #
113
+ # Determines if the current OS is Fedora Linux based distro.
114
+ #
115
+ # @return [Boolean]
116
+ #
117
+ # @api public
118
+ #
119
+ def fedora_linux?
120
+ @linux_distro == :fedora
121
+ end
122
+
123
+ #
124
+ # Determines if the current OS is Debian Linux based distro.
125
+ #
126
+ # @return [Boolean]
127
+ #
128
+ # @api public
129
+ #
130
+ def debian_linux?
131
+ @linux_distro == :debian
132
+ end
133
+
134
+ #
135
+ # Determines if the current OS is SUSE Linux based distro.
136
+ #
137
+ # @return [Boolean]
138
+ #
139
+ # @api public
140
+ #
141
+ def suse_linux?
142
+ @linux_distro == :suse
143
+ end
144
+
145
+ #
146
+ # Determines if the current OS is Arch Linux based distro.
147
+ #
148
+ # @return [Boolean]
149
+ #
150
+ # @api public
151
+ #
152
+ def arch_linux?
153
+ @linux_distro == :arch
154
+ end
155
+ end
156
+ end
157
+ end