nub 0.0.25 → 0.0.27

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8cba0302ba890a2a1332cd5e1e9696ccee3ac8a4235bc86d3d4292de23efa56
4
- data.tar.gz: a35b6c9a6d0925873e19325d903a417c56df346790bcc1f8d96f72f197e7d637
3
+ metadata.gz: 9d5f8ebb6c991951280634bd778890442ead23922928d9a113f6f04449660c5d
4
+ data.tar.gz: 49b25510286dcd087882043bd49209b63bb2688bb338054c1dd3a7d34d5da97c
5
5
  SHA512:
6
- metadata.gz: ca6ff75a2f4e7cf35d9dd0b7440bb82d48946081a01a4646913b0ee1a06da219ffa478d097a7b0dfc2a9dd5356c7cd885adb22985b2cd92115c0f98d2fd7ad7b
7
- data.tar.gz: 0340f674692f7840f4f33a0323d9fce5598991031b26e3b1164f54ec351cc4a7117bb1d24462e8d22e5e99a588a1545b0d5f6a406316af2b00d9e2754aec3630
6
+ metadata.gz: ae5491f1c835a8d9de56127c1b486a875195fd12259834a928c5bc9dd915964114a11c6c8a0b1d795c99d9c121386a72f271d9e48c2b6a66014777a8058c8b77
7
+ data.tar.gz: 1d19f786cfda8a5f21b4913ce42b6b22ad38a4a62bacafb3b72ddea9a4b14556be2d923a3194fb660f82a75977e8ade4573412863caa4993e122d33dba294965
data/lib/nub.rb CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  # Nub is the top level module useful for requiring all sub-modules at once.
24
24
  module Nub
25
- require 'nub/cmds'
25
+ require 'nub/commander'
26
26
  require 'nub/config'
27
27
  require 'nub/log'
28
28
  require 'nub/net'
@@ -0,0 +1,277 @@
1
+ #MIT License
2
+ #Copyright (c) 2018 phR0ze
3
+ #
4
+ #Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ #of this software and associated documentation files (the "Software"), to deal
6
+ #in the Software without restriction, including without limitation the rights
7
+ #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ #copies of the Software, and to permit persons to whom the Software is
9
+ #furnished to do so, subject to the following conditions:
10
+ #
11
+ #The above copyright notice and this permission notice shall be included in all
12
+ #copies or substantial portions of the Software.
13
+ #
14
+ #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ #SOFTWARE.
21
+
22
+ require 'colorize'
23
+
24
+ # Command option encapsulation
25
+ class Option
26
+ attr_reader(:key)
27
+ attr_reader(:short)
28
+ attr_reader(:long)
29
+ attr_reader(:hint)
30
+ attr_reader(:desc)
31
+ attr_reader(:type)
32
+ attr_reader(:allowed)
33
+ attr_reader(:required)
34
+
35
+ # Create a new option instance
36
+ # @param key [String] option short hand, long hand and hint e.g. -s|--skip=COMPONENTS
37
+ # @param desc [String] the option's description
38
+ # @param type [Type] the option's type
39
+ # @param required [Bool] require the option if true else optional
40
+ # @param allowed [Array] array of allowed string values
41
+ def initialize(key, desc, type:nil, required:false, allowed:[])
42
+ @hint = nil
43
+ @long = nil
44
+ @short = nil
45
+ @desc = desc
46
+ @allowed = allowed || []
47
+ @required = required || false
48
+
49
+ # Parse the key into its components (short hand, long hand, and hint)
50
+ #https://bneijt.nl/pr/ruby-regular-expressions/
51
+ # Valid forms to look for with chars [a-zA-Z0-9-_=|]
52
+ # --help, --help=HINT, -h|--help, -h|--help=HINT
53
+ !puts("Error: invalid option key #{key}".colorize(:red)) and
54
+ exit if key && (key.count('=') > 1 or key.count('|') > 1 or !key[/[^\w\-=|]/].nil? or
55
+ key[/(^--[a-zA-Z0-9\-_]+$)|(^--[a-zA-Z\-_]+=\w+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+=\w+$)/].nil?)
56
+ @key = key
57
+ if key
58
+ @hint = key[/.*=(.*)$/, 1]
59
+ @short = key[/^(-\w).*$/, 1]
60
+ @long = key[/(--[\w\-]+)(=.+)*$/, 1]
61
+ else
62
+ # Always require positional options
63
+ @required = true
64
+ end
65
+
66
+ # Validate and set type
67
+ !puts("Error: invalid option type #{type}".colorize(:red)) and
68
+ exit if ![String, Integer, Array, nil].any?{|x| type == x}
69
+ !puts("Error: option type must be set".colorize(:red)) and
70
+ exit if @hint && !type
71
+ @type = String if !key && !type
72
+ @type = FalseClass if key and !type
73
+ @type = type if type
74
+
75
+ # Validate allowed
76
+ if @allowed.any?
77
+ allowed_type = @allowed.first.class
78
+ !puts("Error: mixed allowed types".colorize(:red)) and
79
+ exit if @allowed.any?{|x| x.class != allowed_type}
80
+ end
81
+ end
82
+ end
83
+
84
+ # An implementation of git like command syntax for ruby applications:
85
+ # see https://github.com/phR0ze/ruby-nub
86
+ class Commander
87
+ attr_accessor(:cmds)
88
+ attr_reader(:config)
89
+ attr_reader(:banner)
90
+
91
+ Command = Struct.new(:name, :desc, :opts, :help)
92
+
93
+ # Initialize the commands for your application
94
+ # @param app [String] application name e.g. reduce
95
+ # @param version [String] version of the application e.g. 1.0.0
96
+ # @param examples [String] optional examples to list after the title before usage
97
+ def initialize(app, version, examples:nil)
98
+ @app = app
99
+ @version = version
100
+ @examples = examples
101
+ !puts("Error: app name and version are required".colorize(:red)) and
102
+ exit if @app.nil? or @app.empty? or @version.nil? or @version.empty?
103
+
104
+ @help_opt = Option.new('-h|--help', 'Print command/options help')
105
+ @just = 40
106
+
107
+ # Configuration - ordered list of commands
108
+ @config = []
109
+
110
+ # Incoming user set commands/options
111
+ # {command_name => {}}
112
+ @cmds = {}
113
+ end
114
+
115
+ # Hash like accessor for checking if a command or option is set
116
+ def [](key)
117
+ return @cmds[key] if @cmds[key]
118
+ end
119
+
120
+ # Add a command to the command list
121
+ # @param cmd [String] name of the command
122
+ # @param desc [String] description of the command
123
+ # @param opts [List] list of command options
124
+ def add(cmd, desc, options:[])
125
+ !puts("Error: command names must be pure lowercase letters".colorize(:red)) and
126
+ exit if cmd =~ /[^a-z]/
127
+
128
+ # Build help for command
129
+ help = "#{banner}\n#{desc}\n\nUsage: ./#{@app} #{cmd} [options]\n"
130
+ options << @help_opt
131
+
132
+ # Add positional options first
133
+ sorted_options = options.select{|x| x.key.nil?}
134
+ sorted_options += options.select{|x| !x.key.nil?}.sort{|x,y| x.key <=> y.key}
135
+ positional_index = -1
136
+ sorted_options.each{|x|
137
+ required = x.required ? ", Required" : ""
138
+ allowed = x.allowed.empty? ? "" : " (#{x.allowed * ','})"
139
+ positional_index += 1 if x.key.nil?
140
+ key = x.key.nil? ? "#{cmd}#{positional_index}" : x.key
141
+ type = x.type == FalseClass ? "Flag" : x.type
142
+ help += " #{key.ljust(@just)}#{x.desc}#{allowed}: #{type}#{required}\n"
143
+ }
144
+
145
+ # Create the command in the command config
146
+ @config << Command.new(cmd, desc, sorted_options, help)
147
+ end
148
+
149
+ # Returns banner string
150
+ # @returns [String] the app's banner
151
+ def banner
152
+ banner = "#{@app}_v#{@version}\n#{'-' * 80}".colorize(:light_yellow)
153
+ return banner
154
+ end
155
+
156
+ # Return the app's help string
157
+ # @returns [String] the app's help string
158
+ def help
159
+ help = "#{banner}\n"
160
+ help += "Examples:\n#{@examples}\n\n" if !@examples.nil? && !@examples.empty?
161
+ help += "Usage: ./#{@app} [commands] [options]\n"
162
+ help += " #{'-h|--help'.ljust(@just)}Print command/options help: Flag\n"
163
+ help += "COMMANDS:\n"
164
+ @config.each{|x| help += " #{x.name.ljust(@just)}#{x.desc}\n" }
165
+ help += "\nsee './#{@app} COMMAND --help' for specific command help\n"
166
+
167
+ return help
168
+ end
169
+
170
+ # Construct the command line parser and parse
171
+ def parse!
172
+
173
+ # Set help if nothing was given
174
+ ARGV.clear and ARGV << '-h' if ARGV.empty?
175
+
176
+ # Process global options
177
+ #---------------------------------------------------------------------------
178
+ cmd_names = @config.map{|x| x.name }
179
+ globals = ARGV.take_while{|x| !cmd_names.include?(x)}
180
+ !puts(help) and exit if globals.any?
181
+
182
+ # Process command options
183
+ #---------------------------------------------------------------------------
184
+ loop {
185
+ break if ARGV.first.nil?
186
+
187
+ if !(cmd = @config.find{|x| x.name == ARGV.first}).nil?
188
+ @cmds[ARGV.shift.to_sym] = {}
189
+ cmd_names.reject!{|x| x == cmd.name}
190
+
191
+ # Collect command options
192
+ opts = ARGV.take_while{|x| !cmd_names.include?(x) }
193
+ ARGV.shift(opts.size)
194
+ cmd_pos_opts = cmd.opts.select{|x| x.key.nil? }
195
+ cmd_named_opts = cmd.opts.select{|x| !x.key.nil? }
196
+ !puts("Error: positional option required".colorize(:red)) && !puts(cmd.help) and
197
+ exit if opts.size < cmd_pos_opts.size
198
+
199
+ # Process command options
200
+ pos = -1
201
+ loop {
202
+ break if opts.first.nil?
203
+ opt = opts.shift
204
+ cmd_opt = nil
205
+ value = nil
206
+ sym = nil
207
+
208
+ # Validate/set named options
209
+ # --------------------------------------------------------------------
210
+ # e.g. -s, --skip, --skip=VALUE
211
+ if opt.start_with?('-')
212
+ short = opt[/^(-\w).*$/, 1]
213
+ long = opt[/(--[\w\-]+)(=.+)*$/, 1]
214
+ value = opt[/.*=(.*)$/, 1]
215
+
216
+ # Set symbol converting dashes to underscores for named options
217
+ if (cmd_opt = cmd_named_opts.find{|x| x.short == short || x.long == long})
218
+ sym = cmd_opt.long[2..-1].gsub("-", "_").to_sym
219
+
220
+ # Collect value
221
+ if cmd_opt.type == FalseClass
222
+ value = true if !value
223
+ elsif !value
224
+ value = opts.shift
225
+ end
226
+ end
227
+
228
+ # Validate/set positional options
229
+ # --------------------------------------------------------------------
230
+ else
231
+ pos += 1
232
+ cmd_opt = cmd_pos_opts.shift
233
+ !puts("Error: invalid positional option '#{opt}'".colorize(:red)) && !puts(cmd.help) and
234
+ exit if cmd_opt.nil?
235
+ value = opt
236
+ sym = "#{cmd.name}#{pos}".to_sym
237
+ end
238
+
239
+ # Convert value to appropriate type
240
+ # --------------------------------------------------------------------
241
+ if value
242
+ if cmd_opt.type == Integer
243
+ value = value.to_i
244
+
245
+ # Validate allowed values
246
+ if cmd_opt.allowed.any?
247
+ !puts("Error: invalid integer value '#{value}'".colorize(:red)) && !puts(cmd.help) and
248
+ exit if !cmd_opt.allowed.include?(value)
249
+ end
250
+
251
+ elsif cmd_opt.type == Array
252
+ value = value.split(',')
253
+
254
+ # Validate allowed values
255
+ if cmd_opt.allowed.any?
256
+ value.each{|x|
257
+ !puts("Error: invalid array value '#{x}'".colorize(:red)) && !puts(cmd.help) and
258
+ exit if !cmd_opt.allowed.include?(x)
259
+ }
260
+ end
261
+ end
262
+ end
263
+
264
+ # Set option with value
265
+ # --------------------------------------------------------------------
266
+ !puts("Error: unknown named option '#{opt}' given".colorize(:red)) && !puts(cmd.help) and exit if !sym
267
+ @cmds[cmd.name.to_sym][sym] = value
268
+ }
269
+ end
270
+ }
271
+
272
+ # Ensure all options were consumed
273
+ !puts("Error: invalid options #{ARGV}".colorize(:red)) and exit if ARGV.any?
274
+ end
275
+ end
276
+
277
+ # vim: ft=ruby:ts=2:sw=2:sts=2
data/lib/nub/sys.rb CHANGED
@@ -20,11 +20,26 @@
20
20
  #SOFTWARE.
21
21
 
22
22
  require 'io/console'
23
+ require 'ostruct'
24
+ require 'stringio'
23
25
 
24
- class Sys
26
+ module Sys
27
+
28
+ # Capture STDOUT to a string
29
+ # @returns [String] the redirected output
30
+ def self.capture(&block)
31
+ stdout, stderr = StringIO.new, StringIO.new
32
+ $stdout, $stderr = stdout, stderr
33
+
34
+ result = block.call
35
+
36
+ $stdout, $stderr = STDOUT, STDERR
37
+
38
+ return OpenStruct.new(result: result, stdout: stdout.string, stderr: stderr.string)
39
+ end
25
40
 
26
41
  # Wait for any key to be pressed
27
- def any_key?
42
+ def self.any_key?
28
43
  begin
29
44
  state = `stty -g`
30
45
  `stty raw -echo -icanon isig`
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nub
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.25
4
+ version: 0.0.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick Crummett
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-23 00:00:00.000000000 Z
11
+ date: 2018-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -101,7 +101,7 @@ extensions: []
101
101
  extra_rdoc_files: []
102
102
  files:
103
103
  - lib/nub.rb
104
- - lib/nub/cmds.rb
104
+ - lib/nub/commander.rb
105
105
  - lib/nub/config.rb
106
106
  - lib/nub/log.rb
107
107
  - lib/nub/net.rb
data/lib/nub/cmds.rb DELETED
@@ -1,146 +0,0 @@
1
- #MIT License
2
- #Copyright (c) 2018 phR0ze
3
- #
4
- #Permission is hereby granted, free of charge, to any person obtaining a copy
5
- #of this software and associated documentation files (the "Software"), to deal
6
- #in the Software without restriction, including without limitation the rights
7
- #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- #copies of the Software, and to permit persons to whom the Software is
9
- #furnished to do so, subject to the following conditions:
10
- #
11
- #The above copyright notice and this permission notice shall be included in all
12
- #copies or substantial portions of the Software.
13
- #
14
- #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- #SOFTWARE.
21
-
22
- require 'optparse'
23
- require 'colorize'
24
-
25
- # Command option class provides a way to encapsulate a command with
26
- # any additional properties.
27
- class CmdOpt
28
- attr_reader(:key)
29
- attr_reader(:conf)
30
- attr_reader(:type)
31
- attr_reader(:desc)
32
- attr_reader(:required)
33
- def initialize(conf, desc, type:nil, required:false)
34
- @conf = conf.gsub(' ', '=')
35
- @key = conf.gsub('-', '').split('=').first.to_sym
36
- @type = type
37
- @desc = desc
38
- @required = required
39
- end
40
- end
41
-
42
- # Simple command wrapper around options parsing
43
- # When multiple commands are given they share the options passed along with them
44
- class Cmds
45
-
46
- # Option and command names have all hyphens removed
47
- attr_accessor(:cmds)
48
- attr_accessor(:opts)
49
-
50
- # Initialize the commands for your application
51
- # Params:
52
- # +app+:: application name e.g. reduce
53
- # +version+:: version of the application e.g. 1.0.0
54
- # +examples+:: optional examples to list after the title before usage
55
- def initialize(app, version, examples)
56
- @opts = {}
57
- @cmds = {}
58
- @cmds_config = {}
59
-
60
- @app = app
61
- @version = version
62
- @examples = examples || ''
63
- end
64
-
65
- # Hash like accessor for checking if a command is set
66
- def [](key)
67
- return @cmds[key] if @cmds[key]
68
- return @opts[key] if @opts[key]
69
- end
70
-
71
- # Hash like accessor for editing options
72
- def []=(key, value)
73
- @opts[key] = value
74
- end
75
-
76
- # Add a command to the command list
77
- # Params:
78
- # +cmd+:: name of the command
79
- # +desc+:: description of the command
80
- # +opts+:: list of command options
81
- def add(cmd, desc, opts)
82
- @cmds_config[cmd] = {desc: desc, inopts: opts, outopts: OptionParser.new{|parser|
83
- required = opts.map{|x| x.conf if x.required}.compact * ' '
84
- required += ' ' if not required.empty?
85
- parser.banner = "#{banner}\nUsage: ./#{@app} #{cmd} #{required}[options]"
86
- opts.each{|opt| parser.on(opt.conf, opt.type, opt.desc){|x| @opts[opt.key] = x }}
87
- }}
88
- end
89
-
90
- # Returns banner string
91
- def banner
92
- banner = "#{@app}_v#{@version}\n#{'-' * 80}".colorize(:light_yellow)
93
- return banner
94
- end
95
-
96
- # Construct the command line parser and parse
97
- def parse!
98
-
99
- # Construct help for the application
100
- help = "COMMANDS:\n"
101
- @cmds_config.each{|k,v| help += " #{k.ljust(33, ' ')}#{v[:desc]}\n" }
102
- help += "\nsee './#{@app} COMMAND --help' for specific command info"
103
-
104
- # Construct top level option parser
105
- @optparser = OptionParser.new do |parser|
106
- parser.banner = "#{banner}\n#{@examples}Usage: ./#{@app} commands [options]"
107
- parser.on('-h', '--help', 'Print command/options help') {|x| !puts(parser) and exit }
108
- parser.separator(help)
109
- end
110
-
111
- # Invoke the option parser with help if any un-recognized commands are given
112
- cmds = ARGV.select{|x| not x.start_with?('-')}
113
- ARGV.clear and ARGV << '-h' if ARGV.empty? or cmds.any?{|x| not @cmds_config[x]}
114
- cmds.each{|x| puts("Error: Invalid command '#{x}'".colorize(:red)) if not @cmds_config[x]}
115
- @optparser.order!
116
-
117
- # Now remove them from ARGV leaving only options
118
- ARGV.reject!{|x| not x.start_with?('-')}
119
-
120
- # Parse each command which will consume options from ARGV
121
- cmds.each do |cmd|
122
- begin
123
- @cmds[cmd.gsub('-', '_').to_sym] = true
124
- @cmds_config[cmd][:outopts].order!
125
-
126
- # Ensure that all required options were given
127
- @cmds_config[cmd][:inopts].each{|x|
128
- if x.required and not @opts[x.key]
129
- puts("Error: Missing required option '#{x.key}'".colorize(:red))
130
- ARGV.clear and ARGV << "-h"
131
- @cmds_config[cmd][:outopts].order!
132
- end
133
- }
134
- rescue OptionParser::InvalidOption => e
135
- # Options parser will raise an invalid exception if it doesn't recognize something
136
- # However we want to ignore that as it may be another command's option
137
- ARGV << e.to_s[/(-.*)/, 1]
138
- end
139
- end
140
-
141
- # Ensure all options were consumed
142
- !puts("Error: invalid options #{ARGV}".colorize(:red)) and exit if ARGV.any?
143
- end
144
- end
145
-
146
- # vim: ft=ruby:ts=2:sw=2:sts=2