nrser-rash 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +104 -0
  3. data/.gitmodules +4 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +4 -0
  6. data/.yardopts +7 -0
  7. data/Gemfile +10 -0
  8. data/README.md +4 -0
  9. data/Rakefile +6 -0
  10. data/bash/source-profile.sh +17 -0
  11. data/dev/bin/.gitkeep +0 -0
  12. data/dev/bin/console +33 -0
  13. data/dev/bin/rake +3 -0
  14. data/dev/bin/rash +16 -0
  15. data/dev/bin/rspec +3 -0
  16. data/dev/ref/autocomplete.rb +62 -0
  17. data/dev/scratch/apps.AppleScript +14 -0
  18. data/dev/scratch/blocks.rb +232 -0
  19. data/dev/scratch/decorating_methods.rb +18 -0
  20. data/dev/scratch/functions.sh +80 -0
  21. data/dev/scratch/if.sh +16 -0
  22. data/dev/scratch/inc.rb +3 -0
  23. data/dev/scratch/inheriting_env/main.rb +44 -0
  24. data/dev/scratch/inheriting_env/sub.rb +9 -0
  25. data/dev/scratch/load_main.rb +5 -0
  26. data/dev/scratch/load_module.rb +19 -0
  27. data/dev/scratch/lsregister-dump.txt +30165 -0
  28. data/dev/scratch/main.rb +4 -0
  29. data/dev/scratch/optparse.rb +43 -0
  30. data/dev/scratch/overridding-cd.sh +53 -0
  31. data/dev/scratch/path.sh +22 -0
  32. data/dev/scratch/pirating_methods.rb +62 -0
  33. data/dev/scratch/profile.sh +624 -0
  34. data/dev/scratch/return.sh +5 -0
  35. data/dev/scratch/source_rvm.sh +11 -0
  36. data/dev/scratch/stub-names/project/test +3 -0
  37. data/exe/rash +4 -0
  38. data/lib/nrser/rash/cli/call.rb +137 -0
  39. data/lib/nrser/rash/cli/help.rb +29 -0
  40. data/lib/nrser/rash/cli/list.rb +36 -0
  41. data/lib/nrser/rash/cli/run.rb +54 -0
  42. data/lib/nrser/rash/cli.rb +21 -0
  43. data/lib/nrser/rash/config.rb +172 -0
  44. data/lib/nrser/rash/core_ext/object.rb +55 -0
  45. data/lib/nrser/rash/formatters.rb +105 -0
  46. data/lib/nrser/rash/functions.rb +154 -0
  47. data/lib/nrser/rash/helpers.rb +53 -0
  48. data/lib/nrser/rash/testing.rb +305 -0
  49. data/lib/nrser/rash/util.rb +260 -0
  50. data/lib/nrser/rash/version.rb +17 -0
  51. data/lib/nrser/rash.rb +40 -0
  52. data/nrser-rash.gemspec +48 -0
  53. data/tmp/.gitkeep +0 -0
  54. metadata +248 -0
@@ -0,0 +1,154 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Refinements
5
+ # =======================================================================
6
+
7
+ using NRSER
8
+ using NRSER::Types
9
+
10
+
11
+ # Definitions
12
+ # =======================================================================
13
+
14
+ module NRSER::Rash
15
+ # Users money-patch class methods into this call to expose them to Rash.
16
+ module Functions; end
17
+
18
+ # @!group Rash Function Class Methods
19
+ # ==========================================================================
20
+
21
+ def self.load_functions
22
+ path = config('FUNCTIONS').to_pn
23
+ require path
24
+ end
25
+
26
+
27
+ def self.stub_function?(mod, method_name)
28
+ return false unless mod.method(method_name)
29
+ return false if method_name.to_s[0] == '_'[0]
30
+ true
31
+ end
32
+
33
+
34
+ def self.stub_name namespace, method_name
35
+ (
36
+ (namespace.map {|s| s.downcase}) +
37
+ [method_name]
38
+ ).join(config('STUB_NAMESPACE_SEPERATOR'))
39
+ end
40
+
41
+
42
+ def self.submodule? obj
43
+ obj.is_a?(Module) && (not obj.is_a?(Class))
44
+ end
45
+
46
+
47
+ # TODO: doesn't work. was try to prevent overriding non-rash functions
48
+ # (without some yet to be implemented way of doing so explicitly)
49
+ # 'cause this can cause accidental CHAOS with other scripts
50
+ def self.conflict? name
51
+ return false
52
+ type = `type #{ name } 2>&1`
53
+ if name == 'blah'
54
+ raise type
55
+ end
56
+ return false if $?.exitstatus != 0
57
+ return true
58
+ not type =~ /rash\ call/
59
+ end
60
+
61
+ # Stub out all the singleton methods under {NRSER::Rash::Functions}
62
+ def self.function_names
63
+ call_args = []
64
+ # method to recursivly stub singleton functions in a module
65
+ from_module = -> mod do
66
+ # when the name is split, 'Rash' and 'Functions' take up the
67
+ # first two elements, so drop those to get the namespace
68
+ # as an array of strings
69
+ namespace = mod.name.split( '::' ).drop( 3 )
70
+ # get each singleton method (`def self.*` style)
71
+ mod.singleton_methods.select {|method_name|
72
+ # throw out those that should not be stubbed
73
+ stub_function? mod, method_name
74
+ }.each do |method_name|
75
+
76
+ call_arg = if namespace.empty?
77
+ method_name.to_s
78
+ else
79
+ "#{ namespace.join('.') }.#{ method_name }"
80
+ end
81
+
82
+ call_args << call_arg.downcase
83
+ end
84
+
85
+ # descend into submodules
86
+ mod.constants.map {|name|
87
+ mod.const_get(name)
88
+ }.select {|const|
89
+ # exclude any TestCase classes
90
+ # (const.is_a? Module) && (not const < Minitest::Test)
91
+ submodule? const
92
+ }.each {|submod|
93
+ from_module.call submod
94
+ }
95
+ end # from_module
96
+
97
+ from_module.call NRSER::Rash::Functions
98
+ call_args
99
+ end # .function_names
100
+
101
+
102
+ # stub out all the singleton methods under {NRSER::Rash::Functions}
103
+ def self.function_map
104
+ results = []
105
+ # method to recursively stub singleton functions in a module
106
+ from_module = lambda do |mod|
107
+ # when the name is split, 'Rash' and 'Functions' take up the
108
+ # first two elements, so drop those to get the namespace
109
+ # as an array of strings
110
+ namespace = mod.name.split('::').drop(2)
111
+ # get each singleton method (`def self.*` style)
112
+ mod.singleton_methods.select {|method_name|
113
+ # throw out those that should not be stubbed
114
+ stub_function? mod, method_name
115
+ }.each do |method_name|
116
+
117
+ # the function name is the namespace plus the method name
118
+ # with segments seperated by `'.'`, ex:
119
+ #
120
+ # namespace = ['Nrser', Url']
121
+ # method_name = 'parse'
122
+ # function_name = 'Nrser.Url.parse'
123
+ #
124
+ # funcs << (namespace.clone << method_name)
125
+ function_name = stub_name namespace, method_name
126
+
127
+ call_arg = if namespace.empty?
128
+ method_name.to_s
129
+ else
130
+ "#{ namespace.join('.') }.#{ method_name }"
131
+ end
132
+
133
+ results << {'name' => function_name, 'sig' => call_arg}
134
+ end
135
+
136
+ # descend into submodules
137
+ mod.constants.map {|name|
138
+ mod.const_get(name)
139
+ }.select {|const|
140
+ # exclude any TestCase classes
141
+ # (const.is_a? Module) && (not const < Minitest::Test)
142
+ submodule? const
143
+ }.each {|submod|
144
+ from_module.call submod
145
+ }
146
+ end
147
+
148
+ from_module.call NRSER::Rash::Functions
149
+ results
150
+ end # .function_map
151
+
152
+ # @!endgroup Rash Function Class Methods
153
+
154
+ end # module NRSER::Rash
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Refinements
5
+ # =======================================================================
6
+
7
+ using NRSER
8
+ using NRSER::Types
9
+
10
+
11
+ # Definitions
12
+ # =======================================================================
13
+
14
+ # Helpful and not so helpful things.
15
+ #
16
+ module NRSER::Rash::Helpers
17
+
18
+ include SemanticLogger::Loggable
19
+
20
+ class << self
21
+ def quote(value)
22
+ double_quote(value)
23
+ end
24
+ alias_method :q, :quote
25
+
26
+ # single-quote a string for bash
27
+ # reaplces `'` with `'\''`
28
+ def single_quote(string)
29
+ "'" + string.gsub(/'/, {"'" => "'\\''"}) + "'"
30
+ end
31
+ alias_method :sq, :single_quote
32
+
33
+ # double-quote a string for bash
34
+ # replaces `"` with `\"` and `\` with `\\`
35
+ def double_quote(string)
36
+ '"' + string.gsub(/["\\]/, {'"' => '\"', '\\' => '\\\\'}) + '"'
37
+ end
38
+ alias_method :dq, :double_quote
39
+
40
+ def block(start, finish, &body)
41
+ $stdout.block start, finish, &body
42
+ end
43
+
44
+ def user name = nil, &body
45
+ # if no name is provided, it just means 'not root'
46
+ if name.nil?
47
+ body.() if ENV['USER'] != 'root'
48
+ else
49
+ body.() if ENV['USER'] == name
50
+ end
51
+ end
52
+ end # class << self
53
+ end # module NRSER::Rash::Helpers
@@ -0,0 +1,305 @@
1
+ require 'minitest'
2
+
3
+ module NRSER::Rash
4
+
5
+ # @!group Testing Class Methods
6
+ # ==========================================================================
7
+
8
+ # @todo
9
+ # It seems like testing doesn't work in the state I came back to it
10
+ # 2018.02.22
11
+ #
12
+ #
13
+ # runs tests declared for functions using the `_test_case` and
14
+ # `_test_functions` macros. this is hackety-hack-hackety.
15
+ #
16
+ # rational:
17
+ #
18
+ # i'm using `Test::Unit`. because it comes with the stdlib. `Test::Unit`
19
+ # looks to be based on [MiniTest][https://github.com/seattlerb/minitest],
20
+ # but i can't find great docs on using that either, and i though the
21
+ # `Test::Unit` API might be better known, so i went with that.
22
+ #
23
+ # the only way i can really find using `Test::Unit` documented is
24
+ # to `require 'test/unit'`, which runs all declared `Test::Unit::TestCase`
25
+ # subclasses, reading options off the command line and spitting results
26
+ # back to `STDOUT`.
27
+ #
28
+ # there is an optional `--name <pattern>` arg that you can supply on the
29
+ # command line when invoking a script with a `require 'test/unit' line
30
+ # in it, but it seems like it only matches against method names, not
31
+ # `TestCase` subclasses or anything. under this assumption, the necessary
32
+ # pattern would need to be a part of all test method names. and i don't
33
+ # really want a ton of method names like `test_project_dir_name_space_sub`
34
+ # so that i can turn `rash test Project.dir_name` into a pattern that
35
+ # runs the {NRSER::Rash::Functions::Project.dir_name} tests.
36
+ #
37
+ # the hack:
38
+ #
39
+ # this gets around it by providing macros that will dynamically define
40
+ # `Test::Unit::TestCase` subclasses if and only if those tests are being
41
+ # run.
42
+ #
43
+ # NOTE: why does this start with an '_'?
44
+ def self._test_function(module_or_function_name)
45
+ # if the *first* arg to `rash test` is `--all`, then all test will be run,
46
+ # which is done by supplying an empty `module_or_function_name` to
47
+ # `Testing.start`.
48
+ #
49
+ # TODO: I just realized that it's currently impossible to test all the
50
+ # functions defined in {NRSER::Rash::Functions} without testing all
51
+ # the submodules as well. ugh. solutions include requiring an
52
+ # explicit (never finished I guess...)
53
+ #
54
+ #
55
+ if module_or_function_name == '--all'
56
+ module_or_function_name = ''
57
+ end
58
+ NRSER::Rash::Testing.start module_or_function_name
59
+ # going to need the functions loaded to find it
60
+ load_functions
61
+ if NRSER::Rash::Testing._test_cases.length == 0
62
+ raise NRSER::Rash::Testing::NoTestCasesError.new(module_or_function_name)
63
+ end
64
+ require 'minitest/autorun'
65
+ end
66
+ end # module NRSER::Rash
67
+
68
+ # @!endgroup Testing Class Methods
69
+
70
+
71
+ # @todo
72
+ # It seems like testing doesn't work in the state I came back to it
73
+ # 2018.02.22
74
+ #
75
+ module NRSER::Rash::Testing
76
+ class << self
77
+ attr_accessor :module_or_function_name
78
+ attr_accessor :_test_cases
79
+ attr_accessor :ref_path
80
+ end
81
+
82
+ def self.test
83
+ NRSER::Rash.logger.info "self: #{self.inspect}"
84
+ end
85
+
86
+ def self.start(module_or_function_name)
87
+
88
+ define_test_class
89
+
90
+ self.module_or_function_name = module_or_function_name
91
+ self.ref_path = NRSER::Rash.ref_path(module_or_function_name)
92
+ self._test_cases = []
93
+ end
94
+
95
+ # used to figure out if we're testing
96
+ def self.testing?
97
+ !!module_or_function_name
98
+ end
99
+
100
+ # raised when filtering byt the function name doens't produce any
101
+ # test cases
102
+ class NoTestCasesError < StandardError
103
+ def initialize(name)
104
+ super "no test cases were found for '#{ name }'."
105
+ end
106
+ end
107
+
108
+ class FunctionTestCase < ::Minitest::Test
109
+ class Raises
110
+ attr_reader :cls, :msg
111
+
112
+ def initialize cls, msg = nil
113
+ @cls = cls
114
+ @msg = msg
115
+ end
116
+ end
117
+
118
+ def raises cls, msg = nil
119
+ Raises.new cls, msg
120
+ end
121
+
122
+ def assert_map(hash)
123
+ hash.each do |input, output|
124
+ input = [input] unless input.is_a?(Array)
125
+ # allow mapping to an Exception class as an assert_raise
126
+ if output.is_a?(Raises)
127
+ if output.msg.nil?
128
+ assert_raise output.cls do
129
+ apply_target *input
130
+ end
131
+ else
132
+ assert_raise_with_message(output.cls, output.msg) do
133
+ apply_target *input
134
+ end
135
+ end
136
+ else
137
+ assert_equal output, apply_target(*input)
138
+ end
139
+ end
140
+ end
141
+
142
+ # this was added sometime after 1.9.3, backport:
143
+ def assert_raise_with_message(exception, expected, msg = nil, &block)
144
+ case expected
145
+ when String
146
+ assert = :assert_equal
147
+ when Regexp
148
+ assert = :assert_match
149
+ else
150
+ raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}"
151
+ end
152
+
153
+ ex = assert_raise(exception, *msg) {yield}
154
+ msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"}
155
+
156
+ if assert == :assert_equal
157
+ assert_equal(expected, ex.message, msg)
158
+ else
159
+ msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp ex.message}" }
160
+ assert expected =~ ex.message, msg
161
+ block.binding.eval("proc{|_|$~=_}").call($~)
162
+ end
163
+ ex
164
+ end
165
+ end
166
+
167
+ # create and run a `Minitest::Test` with `&block` as the body,
168
+ # supplying `target_method` as an instance method under it's name
169
+ # and also the name 'target_method'
170
+ def self.test_case(target_method, &block)
171
+ # bail out if we're not in a testing state. this prevents the code
172
+ # from going any further for other commands like `profile` and `call`.
173
+ return unless NRSER::Rash::Testing.testing?
174
+
175
+ method_name = target_method.name.to_s
176
+
177
+ # since we're testing, `Testing.pattern` will be a string the user
178
+ # supplied on the command line, like `Project.dir_name`. we're going
179
+ # to compare it to the method path we're being asked to define
180
+ # the test case for to see if we should acutally create the
181
+ # `Minitest::Test` class.
182
+ method_ref_path = NRSER::Rash.ref_path(target_method.receiver.name,
183
+ target_method.name.to_s)[2..-1]
184
+
185
+ # go through each segment in the ref path we're testing and bail if
186
+ # it doesn't match the coresponding segment in the method's ref path.
187
+ #
188
+ # this only goes up until the testing ref path runs out, which allows
189
+ # the user to specify something like `Url.Parse` to test all the
190
+ # functions under the `NRSER::Rash::Functions::Url::Parse` module.
191
+ Testing.ref_path.each_with_index do |segment, index|
192
+ return unless segment == method_ref_path[index]
193
+ end
194
+
195
+ # if we made it to this point, we should be testing the function and
196
+ # will define the `Test::Unit` subclass:
197
+ _test_case = Class.new NRSER::Rash::Testing::FunctionTestCase do
198
+
199
+ # allow easy access to the target method inside the test methods
200
+ # by defining a method with the same name that points
201
+ # to the target method
202
+ define_method method_name, &target_method
203
+
204
+ # the proxy method should be private so that functions that
205
+ # start with `'test'` aren't run by `Test::Unit`
206
+ private method_name
207
+
208
+ # also define a version named `apply_target` to provide a
209
+ # shortcut for programtic access in helpers like
210
+ # `FunctionTestCase.assert_map` and what not.
211
+ #
212
+ # NOTE: i did this instead of just using `self.class.target_method`
213
+ # as defined below because i wanted the method to be exactly
214
+ # the same as when calling with the previous alias. i might
215
+ # remove it, not sure. it seems redundant.
216
+ define_method :apply_target, &target_method
217
+
218
+ # and include a direct reference the target method in a class
219
+ # instance variable with an attribute reader so it's accessible
220
+ # in case it's needed at some point (it's not at the moment).
221
+ #
222
+ # it can be accessed from inside `FunctionTestCase` subclasses like:
223
+ #
224
+ # self.class.target_method
225
+ #
226
+ @target_method = target_method
227
+ class << self
228
+ attr_reader :target_method
229
+ end
230
+ end
231
+
232
+ # i ran into some "warning: class variable access from toplevel" shit
233
+ # at some point, and this was a part of fixing it. though it no longer
234
+ # seems necessary, i'm going to leave it for a moment:
235
+ # _test_case.instance_eval do
236
+ # @test_method = test_method
237
+ # end
238
+
239
+ # add the user-supplied methods
240
+ _test_case.class_eval &block
241
+
242
+ # programtically create a name for the class and use that to create
243
+ # a constant referencing it in the invoking module, which causes the
244
+ # class to assume that name and module as far as it's `name` method
245
+ # and printing and such, which is nice for the unit test output.
246
+ #
247
+ # names are created by appending the `method_name` to 'Test_', with
248
+ # '!' converted to '_bang' and '?' converted to '_q' (since constants
249
+ # can't have '!' or '?' in them).
250
+ #
251
+ # example:
252
+ #
253
+ # say you had a module like
254
+ #
255
+ # module NRSER::Rash::Functions::SomeNamespace
256
+ # extend NRSER::Rash::Testing::Extensions
257
+ #
258
+ # def self.some_function
259
+ # "hooray!"
260
+ # end
261
+ #
262
+ # _test_case :some_function do
263
+ # def test_some_function
264
+ # assert_equal some_function, "horray!"
265
+ # end
266
+ # end
267
+ # end
268
+ #
269
+ # then you would get a `Minitest::Test` subclass named
270
+ #
271
+ # NRSER::Rash::Functions::SomeNamespace::Test_some_function
272
+ #
273
+ # which seems reasonable enough.
274
+ class_name = "Test_" +
275
+ method_name.to_s.gsub('!', '_bang').gsub('?', '_q')
276
+ const_set class_name, _test_case
277
+
278
+ # and finally, add the class to the `_test_cases` array, which allows
279
+ # us to see what classes were defined after the modules are loaded.
280
+ NRSER::Rash::Testing._test_cases << _test_case
281
+ end
282
+
283
+ # create a `Minitest::Test` subclass with a single test method
284
+ # for `target_method` with `&block` as the body.
285
+ def self.test_function(target_method, &block)
286
+ test_case target_method do
287
+ define_method "test_#{ target_method.name }", &block
288
+ end
289
+ end
290
+
291
+ def self.temp_env hash, &block
292
+ old = Hash[hash.keys.map {|key| ENV[key] }]
293
+ begin
294
+ hash.each do |key, value|
295
+ ENV[key] = value
296
+ end
297
+ block.()
298
+ ensure
299
+ old.each do |key, value|
300
+ ENV[key] = value
301
+ end
302
+ end
303
+ end
304
+
305
+ end # NRSER::Rash::Testing