nrser-rash 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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