vv 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/vv/array_methods.rb +38 -0
- data/lib/vv/cli.rb +304 -0
- data/lib/vv/file_methods.rb +132 -0
- data/lib/vv/hash_methods.rb +36 -0
- data/lib/vv/integer_methods.rb +25 -0
- data/lib/vv/object_methods.rb +39 -0
- data/lib/vv/string_methods.rb +133 -7
- data/lib/vv/symbol_methods.rb +31 -0
- data/lib/vv/utility/automate.rb +80 -2
- data/lib/vv/utility/lookup_table.rb +87 -0
- data/lib/vv/version.rb +1 -1
- data/lib/vv.rb +8 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 052ef19db592bebd51a14310cc4eea838dbd10645f43c05f15cb774292222990
|
4
|
+
data.tar.gz: 2e23028b168d6cb104a0b631c9b80bcb11696120854fbd603126bc962aa380f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a62626e31c21dbe0c5fd426681c2b0007cb4f0a0e844e77a0f1bf5faa9c49edcc7b2da9a4d2e6650220982f95708d3563ffa68090e2371233f6b66f4db780aa9
|
7
|
+
data.tar.gz: 998917dbf37b9900ef500517674d42fad1c64cacbd4aab294a13c29c5c795cbda0926cd0efd0106969287a58fa3fe0f5398e2980e83e39e7786120d54e871f9d
|
data/lib/vv/array_methods.rb
CHANGED
@@ -3,6 +3,7 @@ module VV
|
|
3
3
|
|
4
4
|
def self.included(base)
|
5
5
|
base.extend(ClassMethods)
|
6
|
+
base.attr_accessor :cli_print_separator
|
6
7
|
end
|
7
8
|
|
8
9
|
module ClassMethods
|
@@ -93,6 +94,43 @@ module VV
|
|
93
94
|
self[9]
|
94
95
|
end
|
95
96
|
|
97
|
+
def cli_print width: 80,
|
98
|
+
padding: 0,
|
99
|
+
position: 0,
|
100
|
+
separator: nil
|
101
|
+
|
102
|
+
@cli_print_separator ||= String.space
|
103
|
+
separator ||= @cli_print_separator
|
104
|
+
|
105
|
+
pad_length = padding - position
|
106
|
+
position += pad_length
|
107
|
+
print pad_length.spaces
|
108
|
+
|
109
|
+
separator_required = false
|
110
|
+
self.each do | elem |
|
111
|
+
printable = String.capture_stdout {
|
112
|
+
elem.cli_print width: width,
|
113
|
+
padding: padding,
|
114
|
+
position: position
|
115
|
+
}
|
116
|
+
string = printable.dup
|
117
|
+
string.prepend separator if separator_required
|
118
|
+
delta = string.unstyled.length
|
119
|
+
|
120
|
+
if position + delta > width
|
121
|
+
puts
|
122
|
+
print padding.spaces
|
123
|
+
print printable
|
124
|
+
position = padding + printable.unstyled.length
|
125
|
+
else
|
126
|
+
print string
|
127
|
+
position += delta
|
128
|
+
end
|
129
|
+
separator_required = true
|
130
|
+
end
|
131
|
+
position
|
132
|
+
end
|
133
|
+
|
96
134
|
end
|
97
135
|
|
98
136
|
end
|
data/lib/vv/cli.rb
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
module VV
|
2
|
+
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
attr_reader :option_router,
|
6
|
+
:settings,
|
7
|
+
:cache_path,
|
8
|
+
:config_path,
|
9
|
+
:data_path
|
10
|
+
|
11
|
+
def initialize version: nil,
|
12
|
+
name: nil,
|
13
|
+
config_path: nil,
|
14
|
+
cache_path: nil,
|
15
|
+
data_path: nil
|
16
|
+
|
17
|
+
default_version = "0.0.1"
|
18
|
+
@version = version || default_version
|
19
|
+
|
20
|
+
@config_path = config_path
|
21
|
+
@cache_path = cache_path
|
22
|
+
@data_path = data_path
|
23
|
+
|
24
|
+
@option_router = OptionRouter.new( name: name ) do |router|
|
25
|
+
yield router if block_given?
|
26
|
+
end
|
27
|
+
|
28
|
+
@settings = nil
|
29
|
+
|
30
|
+
self.set_default_paths
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_default_paths
|
34
|
+
@config_path ||= File.join File.config_home, name_version
|
35
|
+
@cache_path ||= File.join File.cache_home, name_version
|
36
|
+
@data_path ||= File.join File.data_home, name_version
|
37
|
+
end
|
38
|
+
|
39
|
+
def name
|
40
|
+
@option_router.name
|
41
|
+
end
|
42
|
+
|
43
|
+
def name_version
|
44
|
+
[ self.name.unstyled, @version ].join("-")
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_flags argv
|
48
|
+
argv = argv.split " " if argv.is_a? String
|
49
|
+
@settings = @option_router.parse argv
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
class OptionRouter
|
55
|
+
|
56
|
+
attr_reader :flag_settings, :name
|
57
|
+
|
58
|
+
attr_accessor :version, :help, :testing
|
59
|
+
|
60
|
+
def initialize name: nil
|
61
|
+
@flag_settings = LookupTable.new
|
62
|
+
@commands = Hash.new
|
63
|
+
@current_flag = nil
|
64
|
+
|
65
|
+
@name = name
|
66
|
+
@name ||= "check".style :lightblue, :italic
|
67
|
+
|
68
|
+
self.set_reserved_flags
|
69
|
+
self.set_reserved_commands
|
70
|
+
|
71
|
+
yield self if block_given?
|
72
|
+
end
|
73
|
+
|
74
|
+
def register flags, type: :string
|
75
|
+
flags = [ flags.to_s ] unless flags.is_a? Array
|
76
|
+
type.one_of! :string,
|
77
|
+
:integer,
|
78
|
+
:decimal,
|
79
|
+
:float,
|
80
|
+
:boolean,
|
81
|
+
:trilean,
|
82
|
+
:ternary,
|
83
|
+
:reserved
|
84
|
+
|
85
|
+
help = block_given? ? yield.squish : nil
|
86
|
+
|
87
|
+
first_flag = flags.first
|
88
|
+
|
89
|
+
flags.each do |flag|
|
90
|
+
if @flag_settings[flag].blank?
|
91
|
+
next if flag == first_flag
|
92
|
+
@flag_settings.alias key: flag, to: first_flag
|
93
|
+
next
|
94
|
+
end
|
95
|
+
|
96
|
+
set_type = @flag_settings[flag][:type]
|
97
|
+
type_ok = set_type != :reserved
|
98
|
+
|
99
|
+
message = "Duplicate flag `#{flag}` cannot be set."
|
100
|
+
fail message if type_ok
|
101
|
+
fail "Reserved flag `#{flag}` cannot be set."
|
102
|
+
end
|
103
|
+
|
104
|
+
settings = { type: type }
|
105
|
+
settings[:help] = help unless help.blank?
|
106
|
+
|
107
|
+
@flag_settings[first_flag] = settings
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_new_flag
|
111
|
+
message = \
|
112
|
+
"Duplicate command line flag #{@flag} encountered."
|
113
|
+
|
114
|
+
@flag = lookup_canonical_flag @flag
|
115
|
+
fail message if @response.include? @flag
|
116
|
+
|
117
|
+
self.set_flag
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_flag
|
121
|
+
@flag = lookup_canonical_flag @flag
|
122
|
+
|
123
|
+
@current_flag = @flag if @value.nil?
|
124
|
+
|
125
|
+
self.set_value
|
126
|
+
end
|
127
|
+
|
128
|
+
def set_value
|
129
|
+
value = @value
|
130
|
+
value &&= @value.to_d if decimal?
|
131
|
+
value ||= true
|
132
|
+
@response[@flag] = value
|
133
|
+
end
|
134
|
+
|
135
|
+
# This needs a refactor.
|
136
|
+
def parse argv
|
137
|
+
@flags_cease = false
|
138
|
+
@response = {}
|
139
|
+
|
140
|
+
argv.each do |arg|
|
141
|
+
next add_input_arg arg if @flags_cease
|
142
|
+
|
143
|
+
@flag, *@value = arg.split String.equals_sign
|
144
|
+
self.standardize_value
|
145
|
+
|
146
|
+
next @flags_cease = true if self.termination_flag?
|
147
|
+
next handle_command if @commands.include? @flag
|
148
|
+
next set_flag if @flag_settings.include? @flag
|
149
|
+
|
150
|
+
self.ensure_known_flag!
|
151
|
+
|
152
|
+
next handle_short_flags if self.short_flag?
|
153
|
+
next handle_current if @current_flag.present?
|
154
|
+
|
155
|
+
self.cease_flag_consideration
|
156
|
+
end
|
157
|
+
|
158
|
+
@response
|
159
|
+
end
|
160
|
+
|
161
|
+
def end_of_commands
|
162
|
+
"--"
|
163
|
+
end
|
164
|
+
|
165
|
+
def reserve flags
|
166
|
+
register flags, type: :reserved
|
167
|
+
end
|
168
|
+
|
169
|
+
def create_flag flag, type: nil
|
170
|
+
raise NotImplementedError
|
171
|
+
end
|
172
|
+
|
173
|
+
def set_reserved_commands
|
174
|
+
set_command :help, alias_flag: "-h"
|
175
|
+
set_command :version, alias_flag: "-V"
|
176
|
+
end
|
177
|
+
|
178
|
+
def set_reserved_flags
|
179
|
+
[ %w[ -h -? --help ],
|
180
|
+
%w[ -V --version ],
|
181
|
+
%w[ -v --verbose ],
|
182
|
+
%w[ -vv --very-verbose ],
|
183
|
+
%w[ -vvv --very-very-verbose ],
|
184
|
+
%w[ -q --quiet ],
|
185
|
+
%w[ -s -qq --absolute-silence ],
|
186
|
+
%w[ -- ] ].each do |flags|
|
187
|
+
|
188
|
+
self.register flags, type: :reserved
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def set_command command, alias_flag: nil
|
193
|
+
command = command.to_s
|
194
|
+
if @commands.include? command
|
195
|
+
raise "Command #{command} already set."
|
196
|
+
end
|
197
|
+
|
198
|
+
@commands[command] = [ :alias_flag, alias_flag ]
|
199
|
+
end
|
200
|
+
|
201
|
+
def lookup_canonical_flag flag
|
202
|
+
@flag_settings.lookup_canonical flag
|
203
|
+
end
|
204
|
+
|
205
|
+
def help_doc
|
206
|
+
ending_flag = %w[ -- ]
|
207
|
+
keys = @flag_settings.canonical_keys - ending_flag
|
208
|
+
keys += ending_flag
|
209
|
+
|
210
|
+
cli_flags = keys.map do |key|
|
211
|
+
flags = [ key ] + @flag_settings.aliases[key].to_a
|
212
|
+
"[#{flags.join(" | ")}]"
|
213
|
+
end
|
214
|
+
|
215
|
+
{ "usage: #{@name}" => cli_flags }
|
216
|
+
end
|
217
|
+
|
218
|
+
def termination_flag?
|
219
|
+
@flag == "--"
|
220
|
+
end
|
221
|
+
|
222
|
+
def handle_command
|
223
|
+
command = @commands[@flag]
|
224
|
+
|
225
|
+
if command.first == :alias_flag
|
226
|
+
@flag = command.second
|
227
|
+
self.set_flag
|
228
|
+
else
|
229
|
+
raise NotImplementedError
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def handle_short_flags
|
234
|
+
short_flags = \
|
235
|
+
@flag.after(String.dash).split String.empty_string
|
236
|
+
|
237
|
+
duplicates = \
|
238
|
+
short_flags.count != short_flags.uniq.count
|
239
|
+
|
240
|
+
message = \
|
241
|
+
"Duplicate command line flags in #{@flag}."
|
242
|
+
fail message if duplicates
|
243
|
+
end
|
244
|
+
|
245
|
+
def handle_current
|
246
|
+
|
247
|
+
@value = @flag
|
248
|
+
@flag = @current_flag
|
249
|
+
|
250
|
+
collection = \
|
251
|
+
@flag_settings[@flag][:type] == :collection
|
252
|
+
if collection
|
253
|
+
@response[@flag] ||= []
|
254
|
+
@response[@flag] << @value
|
255
|
+
else
|
256
|
+
@current_flag = nil
|
257
|
+
self.set_flag
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
def add_input_arg arg
|
263
|
+
@response[:input_arguments] ||= []
|
264
|
+
@response[:input_arguments] << arg
|
265
|
+
end
|
266
|
+
|
267
|
+
def standardize_value
|
268
|
+
present = @value.present?
|
269
|
+
@value = @value.join( String.equals_sign ) if present
|
270
|
+
@value = nil unless @value.present?
|
271
|
+
end
|
272
|
+
|
273
|
+
def short_flag?
|
274
|
+
return false if long_flag?
|
275
|
+
@flag.starts_with? String.dash
|
276
|
+
end
|
277
|
+
|
278
|
+
def long_flag?
|
279
|
+
@flag.starts_with? 2.dashes
|
280
|
+
end
|
281
|
+
|
282
|
+
def ensure_known_flag!
|
283
|
+
message = "Unknown flag `#{@flag}` provided."
|
284
|
+
fail message if @value.present? or self.long_flag?
|
285
|
+
end
|
286
|
+
|
287
|
+
def cease_flag_consideration
|
288
|
+
@flags_cease = true
|
289
|
+
@current_flag = nil
|
290
|
+
@flag.concat String.equals_sign, @value if @value
|
291
|
+
add_input_arg @flag
|
292
|
+
@flag = @value = nil
|
293
|
+
end
|
294
|
+
|
295
|
+
def flag_type
|
296
|
+
@flag_settings[@flag][:type]
|
297
|
+
end
|
298
|
+
|
299
|
+
def decimal?
|
300
|
+
flag_type == :decimal
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
data/lib/vv/file_methods.rb
CHANGED
@@ -75,6 +75,138 @@ module VV
|
|
75
75
|
File::SEPARATOR
|
76
76
|
end
|
77
77
|
|
78
|
+
def copy_into filepath,
|
79
|
+
directory,
|
80
|
+
allow_hidden: true,
|
81
|
+
allow_absolute: true
|
82
|
+
|
83
|
+
message = "Filepath `#{filepath}` is unsafe."
|
84
|
+
fail message unless filepath.safe_path?
|
85
|
+
|
86
|
+
message = "Filepath `#{filepath}` is a directory."
|
87
|
+
fail message if filepath.is_directory_path?
|
88
|
+
|
89
|
+
message = "No such `#{directory}` directory."
|
90
|
+
fail message unless directory.is_directory_path?
|
91
|
+
|
92
|
+
FileUtils.cp filepath, directory
|
93
|
+
end
|
94
|
+
|
95
|
+
def rename_directory from,
|
96
|
+
to,
|
97
|
+
allow_hidden: true,
|
98
|
+
allow_absolute: true
|
99
|
+
safe = from.safe_dir_path? allow_hidden: allow_hidden,
|
100
|
+
allow_absolute: allow_absolute
|
101
|
+
|
102
|
+
safe &&= to.safe_dir_path? allow_hidden: allow_hidden,
|
103
|
+
allow_absolute: allow_absolute
|
104
|
+
|
105
|
+
message = "Refusing to rename unsafe directory"
|
106
|
+
fail message unless safe
|
107
|
+
|
108
|
+
message = "Source #{from} is not a directory"
|
109
|
+
fail message unless from.is_directory_path?
|
110
|
+
|
111
|
+
message = "Target directory name `#{to}` already exists"
|
112
|
+
fail message if to.is_directory_path?
|
113
|
+
|
114
|
+
return FileUtils.mv from, to
|
115
|
+
|
116
|
+
end
|
117
|
+
alias_method :rename_dir, :rename_directory
|
118
|
+
|
119
|
+
# TODO: Think about making a `directory` method on
|
120
|
+
# string so from / to is super clear.
|
121
|
+
def move_directory *args, **kwargs
|
122
|
+
message = \
|
123
|
+
%w[ Moving directories is confusing. Call either
|
124
|
+
`rename_directory` or `move_directory_into`
|
125
|
+
depending on your needs. There are many aliases. ]
|
126
|
+
.spaced
|
127
|
+
|
128
|
+
fail NoMethodError, message
|
129
|
+
end
|
130
|
+
|
131
|
+
def move_directory_into dir,
|
132
|
+
into,
|
133
|
+
allow_hidden: true,
|
134
|
+
allow_absolute: true
|
135
|
+
|
136
|
+
safe = dir.safe_dir_path? allow_hidden: allow_hidden,
|
137
|
+
allow_absolute: allow_absolute
|
138
|
+
|
139
|
+
safe &&= into.safe_dir_path? allow_hidden: allow_hidden,
|
140
|
+
allow_absolute: allow_absolute
|
141
|
+
|
142
|
+
message = "Refusing to rename unsafe directory"
|
143
|
+
fail message unless safe
|
144
|
+
|
145
|
+
message = "Target #{into} is not a directory"
|
146
|
+
fail message unless into.is_directory_path?
|
147
|
+
|
148
|
+
message = "Source #{dir} is not a directory"
|
149
|
+
fail message unless dir.is_directory_path?
|
150
|
+
|
151
|
+
FileUtils.mv dir, into
|
152
|
+
end
|
153
|
+
alias_method :move_dir_into, :move_directory_into
|
154
|
+
alias_method :mv_dir_into, :move_directory_into
|
155
|
+
|
156
|
+
def config_home
|
157
|
+
path = ENV['XDG_CACHE_HOME']
|
158
|
+
return path unless path.blank?
|
159
|
+
path ||= File.join ENV['HOME'], ".config"
|
160
|
+
end
|
161
|
+
alias_method :xdg_config_home, :config_home
|
162
|
+
|
163
|
+
def data_home
|
164
|
+
path = ENV['XDG_DATA_HOME']
|
165
|
+
return path unless path.blank?
|
166
|
+
path ||= File.join ENV['HOME'], ".local", "share"
|
167
|
+
end
|
168
|
+
alias_method :output_home, :data_home
|
169
|
+
alias_method :xdg_data_home, :data_home
|
170
|
+
|
171
|
+
def cache_home sub_directory=nil
|
172
|
+
response = ENV['XDG_CACHE_HOME']
|
173
|
+
response ||= File.join ENV['HOME'], ".cache"
|
174
|
+
|
175
|
+
return response unless sub_directory
|
176
|
+
|
177
|
+
File.join response, sub_directory
|
178
|
+
end
|
179
|
+
alias_method :xdg_cache_home, :cache_home
|
180
|
+
|
181
|
+
def cache_home! sub_directory
|
182
|
+
path = cache_home sub_directory
|
183
|
+
File.make_dir_if_not_exists path
|
184
|
+
path
|
185
|
+
end
|
186
|
+
alias_method :xdg_cache_home!, :cache_home!
|
187
|
+
|
188
|
+
def make_directory_if_not_exists directory
|
189
|
+
FileUtils.mkdir_p(directory).first
|
190
|
+
end
|
191
|
+
alias_method :make_dir_if_not_exists,
|
192
|
+
:make_directory_if_not_exists
|
193
|
+
|
194
|
+
def make_directory directory
|
195
|
+
FileUtils.mkdir(directory).first
|
196
|
+
end
|
197
|
+
alias_method :create_directory, :make_directory
|
198
|
+
alias_method :create_dir, :make_directory
|
199
|
+
alias_method :make_dir, :make_directory
|
200
|
+
|
201
|
+
def remove_directory directory, quiet_if_gone: false
|
202
|
+
no_directory_exists = ! directory.is_directory_path?
|
203
|
+
return if quiet_if_gone && no_directory_exists
|
204
|
+
FileUtils.remove_dir directory
|
205
|
+
end
|
206
|
+
alias_method :rm_directory, :remove_directory
|
207
|
+
alias_method :remove_dir, :remove_directory
|
208
|
+
alias_method :rm_dir, :remove_directory
|
209
|
+
|
78
210
|
end
|
79
211
|
|
80
212
|
def vv_readlines
|
data/lib/vv/hash_methods.rb
CHANGED
@@ -38,5 +38,41 @@ module VV
|
|
38
38
|
self
|
39
39
|
end
|
40
40
|
|
41
|
+
def cli_print width: 80,
|
42
|
+
position: 0,
|
43
|
+
padding: 0
|
44
|
+
|
45
|
+
key_padding = nil
|
46
|
+
|
47
|
+
String.capture_stdout {
|
48
|
+
key_padding = self.keys.map { | key |
|
49
|
+
key.cli_print width: width,
|
50
|
+
position: position,
|
51
|
+
padding: padding
|
52
|
+
}.max
|
53
|
+
}
|
54
|
+
|
55
|
+
key_padding += 1
|
56
|
+
|
57
|
+
self.each do | key, value |
|
58
|
+
position = key.cli_print width: width,
|
59
|
+
position: position,
|
60
|
+
padding: padding
|
61
|
+
|
62
|
+
print ( key_padding - position ).spaces
|
63
|
+
|
64
|
+
value_padding = position = key_padding
|
65
|
+
|
66
|
+
position = value.cli_print width: width,
|
67
|
+
position: position,
|
68
|
+
padding: value_padding
|
69
|
+
|
70
|
+
puts
|
71
|
+
position = 0
|
72
|
+
end
|
73
|
+
|
74
|
+
position
|
75
|
+
end
|
76
|
+
|
41
77
|
end
|
42
78
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Integer
|
2
|
+
|
3
|
+
def spaces
|
4
|
+
characters String.space
|
5
|
+
end
|
6
|
+
|
7
|
+
def dashes
|
8
|
+
characters String.dash
|
9
|
+
end
|
10
|
+
|
11
|
+
def characters character, fail_on_negative: false
|
12
|
+
message = "Expected single character, not #{character}."
|
13
|
+
fail ArgumentError, message if character.length > 1
|
14
|
+
|
15
|
+
message = "Expected non-negative integer, not `#{self}`."
|
16
|
+
fail message if self < 0 and fail_on_negative
|
17
|
+
|
18
|
+
( self > 0 ) ? ( character * self ) : String.empty_string
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_i!
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/vv/object_methods.rb
CHANGED
@@ -1,11 +1,50 @@
|
|
1
1
|
class Object
|
2
2
|
|
3
|
+
alias_method :responds_to?, :respond_to?
|
4
|
+
|
3
5
|
def blank?
|
4
6
|
respond_to?(:empty?) ? !!empty? : !self
|
5
7
|
end unless method_defined? :blank?
|
6
8
|
|
9
|
+
def cli_printable **kwargs
|
10
|
+
String.get_stdout { self.cli_print( **kwargs ) }
|
11
|
+
rescue NoMethodError
|
12
|
+
message = \
|
13
|
+
"`cli_printable` requires `cli_print` on child class"
|
14
|
+
fail NoMethodError, message
|
15
|
+
end unless method_defined? :cli_printable
|
16
|
+
|
7
17
|
def present?
|
8
18
|
!blank?
|
9
19
|
end unless method_defined? :present?
|
10
20
|
|
21
|
+
def one_of? *collection, mixed: false, allow_unsafe: false
|
22
|
+
nested = collection.first.is_a? Array
|
23
|
+
nested ||= collection.first.is_a? Hash
|
24
|
+
nested ||= collection.first.is_a? Set
|
25
|
+
|
26
|
+
message = \
|
27
|
+
"Unexpected nested argument. If desired set `allow_unsafe: true`."
|
28
|
+
fail ArgumentError, message if nested unless allow_unsafe
|
29
|
+
|
30
|
+
return collection.include? self if mixed
|
31
|
+
|
32
|
+
klass = self.class
|
33
|
+
ok = collection.reject {|s| s.is_a? klass }.blank?
|
34
|
+
|
35
|
+
message = "Invalid types: #{klass} collection required."
|
36
|
+
fail ArgumentError, message unless ok
|
37
|
+
|
38
|
+
collection.include? self
|
39
|
+
end
|
40
|
+
|
41
|
+
def one_of! *collection, mixed: false
|
42
|
+
return true if self.one_of?( *collection, mixed: mixed )
|
43
|
+
|
44
|
+
klass = self.class
|
45
|
+
collection = collection.stringify_collection grave: true
|
46
|
+
message = "#{klass} `#{self}` is invalid. Must be one of: #{collection}."
|
47
|
+
fail message
|
48
|
+
end
|
49
|
+
|
11
50
|
end
|
data/lib/vv/string_methods.rb
CHANGED
@@ -25,12 +25,22 @@ module VV
|
|
25
25
|
("A".."Z").to_a
|
26
26
|
end
|
27
27
|
|
28
|
-
def letters_and_numbers
|
28
|
+
def letters_and_numbers capitals: false
|
29
29
|
response = self.letters
|
30
30
|
response += self.capitals if capitals
|
31
31
|
response += self.numbers
|
32
32
|
end
|
33
33
|
|
34
|
+
def capture_stdout(&block)
|
35
|
+
original_stdout = $stdout
|
36
|
+
$stdout = StringIO.new
|
37
|
+
yield
|
38
|
+
$stdout.string
|
39
|
+
ensure
|
40
|
+
$stdout = original_stdout
|
41
|
+
end
|
42
|
+
alias_method :get_stdout, :capture_stdout
|
43
|
+
|
34
44
|
end
|
35
45
|
|
36
46
|
module SharedInstanceAndClassMethods
|
@@ -47,14 +57,26 @@ module VV
|
|
47
57
|
"-"
|
48
58
|
end
|
49
59
|
|
60
|
+
def equals_sign
|
61
|
+
"="
|
62
|
+
end
|
63
|
+
|
50
64
|
def period
|
51
65
|
"."
|
52
66
|
end
|
53
67
|
|
68
|
+
def colon
|
69
|
+
":"
|
70
|
+
end
|
71
|
+
|
54
72
|
def underscore_character
|
55
73
|
"_"
|
56
74
|
end
|
57
75
|
|
76
|
+
def space
|
77
|
+
" "
|
78
|
+
end
|
79
|
+
|
58
80
|
def newline
|
59
81
|
"\n"
|
60
82
|
end
|
@@ -75,6 +97,12 @@ module VV
|
|
75
97
|
|
76
98
|
end
|
77
99
|
|
100
|
+
# Instance methods start
|
101
|
+
|
102
|
+
def includes? *args
|
103
|
+
self.include?(*args)
|
104
|
+
end
|
105
|
+
|
78
106
|
def starts_with? *args
|
79
107
|
self.start_with?(*args)
|
80
108
|
end
|
@@ -98,6 +126,10 @@ module VV
|
|
98
126
|
self.chomp(string) + string
|
99
127
|
end
|
100
128
|
|
129
|
+
def with_newline
|
130
|
+
self.with_ending newline
|
131
|
+
end
|
132
|
+
|
101
133
|
def squish!
|
102
134
|
self.gsub!(/\A[[:space:]]+/, "")
|
103
135
|
self.gsub!(/[[:space:]]+\z/, "")
|
@@ -146,9 +178,10 @@ module VV
|
|
146
178
|
self.to_regex_filter
|
147
179
|
end
|
148
180
|
|
149
|
-
def safe_filename?
|
181
|
+
def safe_filename?( allow_hidden: false )
|
150
182
|
unsafe = self.blank?
|
151
|
-
|
183
|
+
|
184
|
+
unsafe ||= self.starts_with?(period) unless allow_hidden
|
152
185
|
unsafe ||= self.starts_with? dash
|
153
186
|
|
154
187
|
unsafe ||= self.end_with? period
|
@@ -159,16 +192,52 @@ module VV
|
|
159
192
|
! unsafe
|
160
193
|
end
|
161
194
|
|
162
|
-
def safe_path?
|
195
|
+
def safe_path?( allow_hidden: false, allow_absolute: false )
|
196
|
+
safe = self.safe_dir_path? allow_hidden: allow_hidden,
|
197
|
+
allow_absolute: allow_absolute
|
198
|
+
|
199
|
+
unsafe = ( ! safe )
|
200
|
+
unsafe ||= self.ends_with? File::SEPARATOR
|
201
|
+
|
202
|
+
! unsafe
|
203
|
+
end
|
204
|
+
|
205
|
+
def safe_dir_path? allow_hidden: false,
|
206
|
+
allow_absolute: true
|
163
207
|
separator = File::SEPARATOR
|
164
208
|
|
165
|
-
unsafe =
|
166
|
-
unsafe ||= self.
|
167
|
-
unsafe ||= self.
|
209
|
+
unsafe = false
|
210
|
+
unsafe ||= self.starts_with?(separator) unless allow_absolute
|
211
|
+
unsafe ||= self.after(separator, safe: false)
|
212
|
+
.split(separator).map do |fragment|
|
213
|
+
fragment.safe_filename? allow_hidden: allow_hidden
|
214
|
+
end.map(&:!).any?
|
168
215
|
|
169
216
|
! unsafe
|
170
217
|
end
|
171
218
|
|
219
|
+
def is_directory_path?
|
220
|
+
File.directory? self
|
221
|
+
end
|
222
|
+
|
223
|
+
def is_file_path?
|
224
|
+
File.file? self
|
225
|
+
end
|
226
|
+
|
227
|
+
def file_join *args
|
228
|
+
unsafe = args.reject(&:safe_path?)
|
229
|
+
|
230
|
+
return File.join self, *args if unsafe.blank?
|
231
|
+
|
232
|
+
frags = unsafe.first(3).stringify_collection grave: true
|
233
|
+
count = unsafe.count
|
234
|
+
|
235
|
+
message = \
|
236
|
+
"#{count} unsafe path fragments including: #{frags}"
|
237
|
+
|
238
|
+
fail ArgumentError, message
|
239
|
+
end
|
240
|
+
|
172
241
|
def hex?
|
173
242
|
return false if self.blank?
|
174
243
|
match_non_hex_digits = /\H/
|
@@ -225,6 +294,17 @@ module VV
|
|
225
294
|
t: 1000_000_000_000 }.stringify_keys
|
226
295
|
end
|
227
296
|
|
297
|
+
def unstyle
|
298
|
+
self.gsub( /\e\[+\d+m/, empty_string )
|
299
|
+
.gsub( /\e\[((\d+)+\;)+\d+m/, empty_string )
|
300
|
+
end
|
301
|
+
alias_method :unstyled, :unstyle
|
302
|
+
|
303
|
+
def unstyle!
|
304
|
+
self.gsub!( /\e\[+\d+m/, empty_string )
|
305
|
+
self.gsub!( /\e\[((\d+)+\;)+\dm/, empty_string )
|
306
|
+
end
|
307
|
+
|
228
308
|
def style *args
|
229
309
|
color = bold = underline = italic = nil
|
230
310
|
|
@@ -273,6 +353,10 @@ module VV
|
|
273
353
|
"@#{self}"
|
274
354
|
end
|
275
355
|
|
356
|
+
def insta_sym
|
357
|
+
self.insta.to_sym
|
358
|
+
end
|
359
|
+
|
276
360
|
def to position
|
277
361
|
self[0..position]
|
278
362
|
end
|
@@ -381,5 +465,47 @@ module VV
|
|
381
465
|
self[9]
|
382
466
|
end
|
383
467
|
|
468
|
+
def cli_print width: 80,
|
469
|
+
padding: 0,
|
470
|
+
position: 0,
|
471
|
+
hard_wrap: false
|
472
|
+
|
473
|
+
raise NotImplemented if hard_wrap
|
474
|
+
raise NotImplemented if self.includes? newline
|
475
|
+
|
476
|
+
pad_length = padding - position
|
477
|
+
position += pad_length
|
478
|
+
print pad_length.spaces
|
479
|
+
|
480
|
+
unstyled_length = self.unstyled.length
|
481
|
+
remaining_length = width - position
|
482
|
+
if unstyled_length <= remaining_length
|
483
|
+
print self
|
484
|
+
position += unstyled_length
|
485
|
+
return position
|
486
|
+
end
|
487
|
+
|
488
|
+
space_index = self[0..remaining_length].rindex(" ")
|
489
|
+
space_index ||= self.index(" ")
|
490
|
+
|
491
|
+
if space_index
|
492
|
+
sub = self[0..space_index]
|
493
|
+
print sub
|
494
|
+
puts
|
495
|
+
position = 0
|
496
|
+
start = space_index + 1
|
497
|
+
return self[start..-1].cli_print width: width,
|
498
|
+
padding: padding,
|
499
|
+
position: position,
|
500
|
+
hard_wrap: hard_wrap
|
501
|
+
else
|
502
|
+
print self
|
503
|
+
puts
|
504
|
+
position = 0
|
505
|
+
end
|
506
|
+
|
507
|
+
return position
|
508
|
+
end
|
509
|
+
|
384
510
|
end
|
385
511
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module VV
|
2
|
+
module SymbolMethods
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def vv_included?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def insta
|
15
|
+
self.to_s.insta
|
16
|
+
end
|
17
|
+
|
18
|
+
def insta_sym
|
19
|
+
self.to_s.insta_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
def plural? *args, **kwargs
|
23
|
+
self.to_s.plural?( *args, **kwargs )
|
24
|
+
end
|
25
|
+
|
26
|
+
def singular? *args, **kwargs
|
27
|
+
self.to_s.singular?( *args, **kwargs )
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/vv/utility/automate.rb
CHANGED
@@ -73,11 +73,89 @@ module VV
|
|
73
73
|
|
74
74
|
def push( name: )
|
75
75
|
puts %x{ TAG_VERSION=$(./bin/version)
|
76
|
+
git push origin HEAD && \\
|
77
|
+
gem push $(readlink -f #{name}-*.gem | sort | tail -n 1) && \\
|
76
78
|
git push origin v${TAG_VERSION}
|
77
|
-
|
78
|
-
}
|
79
|
+
}
|
79
80
|
end
|
80
81
|
module_function :push
|
81
82
|
|
83
|
+
def run command: nil, annoying_command: nil
|
84
|
+
command ||= "rake"
|
85
|
+
annoying_command ||= command
|
86
|
+
args = ARGV.to_a
|
87
|
+
|
88
|
+
# breaking out of binding.pry is hard without exec
|
89
|
+
exec command if args.empty?
|
90
|
+
|
91
|
+
return annoying_command(args, command: annoying_command)
|
92
|
+
end
|
93
|
+
module_function :run
|
94
|
+
|
95
|
+
def annoying_command args, command: nil
|
96
|
+
command ||= "rake"
|
97
|
+
|
98
|
+
test_dir = File.join File.expand_path(Dir.pwd), "test"
|
99
|
+
temp_test_dir = "temp_test_dir_#{Random.identifier 6}"
|
100
|
+
|
101
|
+
message = "Cannot find test directory `#{test_dir}`."
|
102
|
+
fail message unless test_dir.is_directory_path?
|
103
|
+
|
104
|
+
fail "Unexpected arg count" if args.size > 2
|
105
|
+
|
106
|
+
helper_file = "test_helper.rb"
|
107
|
+
file, line_number = args
|
108
|
+
|
109
|
+
if line_number.nil?
|
110
|
+
file, line_number = file.split(String.colon)[0..1]
|
111
|
+
end
|
112
|
+
|
113
|
+
File.rename_directory test_dir, temp_test_dir
|
114
|
+
sleep 0.01
|
115
|
+
File.make_directory test_dir
|
116
|
+
|
117
|
+
path = file.split(File.separator).last
|
118
|
+
new_filename = temp_test_dir.file_join path
|
119
|
+
helper_file = temp_test_dir.file_join helper_file
|
120
|
+
File.copy_into new_filename, test_dir
|
121
|
+
File.copy_into helper_file, test_dir
|
122
|
+
|
123
|
+
if line_number
|
124
|
+
line_number = line_number.to_i!
|
125
|
+
i = j = line_number - 1
|
126
|
+
lines = File.vv_readlines test_dir.file_join(path)
|
127
|
+
|
128
|
+
while i > 0
|
129
|
+
line = lines[i]
|
130
|
+
break if line.start_with? " def "
|
131
|
+
i -= 1
|
132
|
+
end
|
133
|
+
|
134
|
+
while j < lines.count
|
135
|
+
line = lines[j]
|
136
|
+
break if line.start_with? " end"
|
137
|
+
j += 1
|
138
|
+
end
|
139
|
+
content = (lines[0..3] + lines[i..j] + lines[-2..-1] ).join("\n")
|
140
|
+
content += "\n"
|
141
|
+
|
142
|
+
File.write test_dir.file_join(path), content
|
143
|
+
end
|
144
|
+
|
145
|
+
full_command = \
|
146
|
+
[ command,
|
147
|
+
"rm -r #{test_dir}",
|
148
|
+
"mv #{temp_test_dir} #{test_dir}" ].join(" && ")
|
149
|
+
|
150
|
+
exec full_command
|
151
|
+
|
152
|
+
# The below shouldn't run in normal operation, since
|
153
|
+
# exec will replace the current process
|
154
|
+
ensure
|
155
|
+
File.remove_directory test_dir
|
156
|
+
File.rename_directory temp_test_dir, test_dir
|
157
|
+
end
|
158
|
+
module_function :annoying_command
|
159
|
+
|
82
160
|
end
|
83
161
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class LookupTable
|
2
|
+
|
3
|
+
attr_reader :canonnicals, :aliases, :data
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@canonicals = Hash.new
|
7
|
+
@aliases = Hash.new
|
8
|
+
@data = Hash.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def alias( key:, to: )
|
12
|
+
_ensure_alias_possible key
|
13
|
+
|
14
|
+
@canonicals[key] = to
|
15
|
+
|
16
|
+
@aliases[to] ||= Set.new
|
17
|
+
@aliases[to] << key
|
18
|
+
end
|
19
|
+
|
20
|
+
def []= key, value
|
21
|
+
@data[ self.canonical key ] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def [] key
|
25
|
+
@data[ self.canonical key ]
|
26
|
+
end
|
27
|
+
|
28
|
+
def canonical key
|
29
|
+
@canonicals[key] || key
|
30
|
+
end
|
31
|
+
|
32
|
+
def canonical_keys
|
33
|
+
@data.keys + (@aliases.keys - @data.keys)
|
34
|
+
end
|
35
|
+
|
36
|
+
def include? key
|
37
|
+
@data.include?( self.canonical key )
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_h
|
41
|
+
keys = self.canonical_keys
|
42
|
+
|
43
|
+
keys.inject({}) {|acc, key|
|
44
|
+
data = @data[key] || {}
|
45
|
+
aliases = @aliases[key] || {}
|
46
|
+
acc[key] = { data: data, aliases: aliases.to_a }
|
47
|
+
acc
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Make Object class method called `delegate_missing`?
|
52
|
+
def method_missing(method, *args, **kwargs, &block)
|
53
|
+
super
|
54
|
+
rescue NoMethodError
|
55
|
+
begin
|
56
|
+
if args.size > 0 && kwargs.size > 0
|
57
|
+
return self.to_h.public_send method, *args, **kwargs, &block
|
58
|
+
elsif args.size > 0
|
59
|
+
return self.to_h.public_send method, *args, &block
|
60
|
+
elsif kwargs.size > 0
|
61
|
+
return self.to_h.public_send method, **kwargs, &block
|
62
|
+
else
|
63
|
+
return self.to_h.public_send method, &block
|
64
|
+
end
|
65
|
+
rescue NoMethodError
|
66
|
+
end
|
67
|
+
raise
|
68
|
+
end
|
69
|
+
|
70
|
+
def lookup_canonical key
|
71
|
+
return key if self.canonical_keys.include? key
|
72
|
+
|
73
|
+
@canonicals[key]
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def _ensure_alias_possible key
|
79
|
+
return if @aliases[key].blank?
|
80
|
+
aliases = @aliases[key]
|
81
|
+
count = aliases.count
|
82
|
+
message = \
|
83
|
+
"Cannot alias `#{key}` because #{count} others currently alias it."
|
84
|
+
fail message
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/lib/vv/version.rb
CHANGED
data/lib/vv.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
require 'securerandom'
|
2
|
+
require 'set'
|
3
|
+
require 'bigdecimal'
|
4
|
+
require 'bigdecimal/util'
|
5
|
+
require 'fileutils'
|
2
6
|
|
3
7
|
require_relative "vv/gem_methods"
|
4
8
|
|
@@ -6,6 +10,10 @@ Gem.require_files "vv/*.rb"
|
|
6
10
|
Gem.require_files "vv/style/*.rb"
|
7
11
|
Gem.require_files "vv/utility/*.rb"
|
8
12
|
|
13
|
+
class Symbol
|
14
|
+
include VV::SymbolMethods
|
15
|
+
end
|
16
|
+
|
9
17
|
class String
|
10
18
|
include VV::StringMethods
|
11
19
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Aysan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -63,10 +63,12 @@ files:
|
|
63
63
|
- README.markdown
|
64
64
|
- lib/vv.rb
|
65
65
|
- lib/vv/array_methods.rb
|
66
|
+
- lib/vv/cli.rb
|
66
67
|
- lib/vv/false_methods.rb
|
67
68
|
- lib/vv/file_methods.rb
|
68
69
|
- lib/vv/gem_methods.rb
|
69
70
|
- lib/vv/hash_methods.rb
|
71
|
+
- lib/vv/integer_methods.rb
|
70
72
|
- lib/vv/nil_methods.rb
|
71
73
|
- lib/vv/object_methods.rb
|
72
74
|
- lib/vv/random_methods.rb
|
@@ -77,8 +79,10 @@ files:
|
|
77
79
|
- lib/vv/style/format.rb
|
78
80
|
- lib/vv/style/italic.rb
|
79
81
|
- lib/vv/style/underline.rb
|
82
|
+
- lib/vv/symbol_methods.rb
|
80
83
|
- lib/vv/true_methods.rb
|
81
84
|
- lib/vv/utility/automate.rb
|
85
|
+
- lib/vv/utility/lookup_table.rb
|
82
86
|
- lib/vv/version.rb
|
83
87
|
homepage: https://github.com/zachaysan/vv
|
84
88
|
licenses:
|