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,137 @@
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::CLI
15
+
16
+ # Call a Rash function.
17
+ #
18
+ # @param [String] name
19
+ # Name of the function.
20
+ #
21
+ # @param [Array<String>] argv
22
+ # The shell arguments.
23
+ #
24
+ def self.call name, argv
25
+
26
+ logger.trace "calling #{ name.inspect }, argv: #{ argv.inspect }"
27
+ # look for options
28
+ options = {}
29
+ args = argv.reject do |arg|
30
+ # long options of form '--<name>=<value>'
31
+ if m = arg.match(/\A--([^=]*)(=?)(.*)/)
32
+ options[m[1].to_sym] = if m[2] == ''
33
+ true
34
+ else
35
+ m[3]
36
+ end
37
+ true
38
+ # short options of form '-<char>'
39
+ elsif arg.start_with? '-'
40
+ arg[1..-1].each_char do |char|
41
+ options[char] = true
42
+ end
43
+ true
44
+ end
45
+ end
46
+ logger.debug "options: #{ options.inspect }"
47
+ # options are the last arg, unless we didn't find any
48
+ #
49
+ # NOTE: this causes functions without an optional `options` arg
50
+ # on the end to raise an exception when called with any options.
51
+ #
52
+ # i think this is the correct behavior: the function can't handle
53
+ # options, and should error out.
54
+ args << options unless options.empty?
55
+
56
+ NRSER::Rash.load_functions
57
+ if name.include? '.'
58
+ namespace, method_name = name.split '.'
59
+ namespace = namespace.split '::'
60
+ else
61
+ namespace = []
62
+ method_name = name
63
+ end
64
+ mod = NRSER::Rash::Functions
65
+ namespace.each do |submod_name|
66
+ mod = mod.const_get(submod_name.capitalize)
67
+ end
68
+ begin
69
+ rtn = mod.method(method_name).call *args
70
+ rescue Exception => error
71
+ if NRSER::Rash.config( 'PRINT_ERRORS' ).truthy?
72
+ if NRSER::Rash.config( 'STACKTRACE' ).truthy?
73
+ raise error
74
+ else
75
+ logger.fatal error
76
+ # this is the error code that ruby seems to exit with when you
77
+ # don't check the exception
78
+ exit 1
79
+ end
80
+ end
81
+ end
82
+ logger.debug "return value: #{ rtn.inspect }"
83
+ if formatted = format(rtn)
84
+ puts formatted
85
+ end
86
+ exit 0
87
+ end
88
+
89
+ # formats an object to write to `$stdout`.
90
+ # returns `nil` if there's nothing to write (different than returning '',
91
+ # indicating the empty string should be written).
92
+ def self.format(obj)
93
+ # TODO: should formatter be overridable with a env var?
94
+ # formatter = begin config('FORMATTER'); rescue NameError => e; nil; end
95
+ # logger.debug "formatter: #{ formatter.inspect }"
96
+ if obj.rash_formatter
97
+ # there is some sort of formatting info
98
+ # even if the value is a string, we want to follow the instructions
99
+ # hell, maybe we want to write the string out as json or something :/
100
+ if obj.rash_formatter.respond_to? :call
101
+ # the value responds to `call`, so call it to get the value
102
+ # TODO: should we check that it returns a `String`?
103
+ obj.rash_formatter.call(obj)
104
+ # NOTE: `Object.singleton_methods` returns an array of strings in 1.8
105
+ # and an array of symbols in 1.9, so use strings to work in
106
+ # both.
107
+ elsif NRSER::Rash::Formatters.
108
+ singleton_methods.
109
+ map {|_| _.to_s }.
110
+ include? obj.rash_formatter.to_s
111
+ # it's a symbol that identifies on of the {NRSER::Rash::Formatters}
112
+ # class methods, so call that
113
+ NRSER::Rash::Formatters.send obj.rash_formatter, obj
114
+ else
115
+ # whatever, call the default
116
+ NRSER::Rash::Formatters.send config('DEFAULT_FORMATTER'), obj
117
+ end
118
+ else
119
+ # there is no formatting info
120
+ if obj.is_a? String
121
+ # in the case that a method passed back a `String`, i think we should
122
+ # consider it formatted and just print it
123
+ obj
124
+ elsif obj.nil?
125
+ # the result is nil and there has been no attempt to format it in
126
+ # a specific way that some program might need to recongize or
127
+ # something. at this point, i'm going to say that the function
128
+ # took some successful action and doesn't have anyhting to say about
129
+ # it, which i think means we shouldn't print anything
130
+ else
131
+ # otherwise, send it to the default formatter
132
+ NRSER::Rash::Formatters.send config('DEFAULT_FORMATTER'), obj
133
+ end
134
+ end
135
+ end
136
+
137
+ end # module NRSER::Rash::CLI
@@ -0,0 +1,29 @@
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::CLI
15
+
16
+ def self.help
17
+ # TODO: woefully incomplete
18
+ puts <<~EOF
19
+ Usage: rash COMMAND [ARGS...]
20
+
21
+ Commands:
22
+ list List functions you can call.
23
+ call Call a method from NRSER::Rash::Functions.
24
+ test Run tests for a method from NRSER::Rash::Functions.
25
+ help This message.
26
+ EOF
27
+ end
28
+
29
+ end # module NRSER::Rash::CLI
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Stdlib
8
+ # -----------------------------------------------------------------------
9
+
10
+ # Deps
11
+ # -----------------------------------------------------------------------
12
+
13
+ # Project / Package
14
+ # -----------------------------------------------------------------------
15
+
16
+
17
+ # Refinements
18
+ # =======================================================================
19
+
20
+
21
+ # Declarations
22
+ # =======================================================================
23
+
24
+
25
+ # Definitions
26
+ # =======================================================================
27
+
28
+ module NRSER::Rash::CLI
29
+
30
+ # List the Rash functions available.
31
+ def self.list
32
+ NRSER::Rash.load_functions
33
+ puts NRSER::Rash.function_names
34
+ end
35
+
36
+ end # module NRSER::Rash
@@ -0,0 +1,54 @@
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::CLI
15
+
16
+ # start here.
17
+ #
18
+ # this is the entry point for the script when executed. it expects a
19
+ # command as the first argument and dispatches to the associated method.
20
+ def self.run
21
+ SemanticLogger.add_appender io: $stderr, formatter: {
22
+ color: {
23
+ ap: {multiline: true},
24
+ }
25
+ }
26
+ SemanticLogger.default_level = NRSER::Rash.config( 'LOG_LEVEL' ).to_sym
27
+
28
+ # the command is the first argument
29
+ # we won't need or want it after this, so delete it
30
+ command = ARGV.shift
31
+ # filter out the command line config options
32
+ NRSER::Rash.filter_and_set_config ARGV
33
+ case command
34
+ when 'call'
35
+ # we are being asked to call a function. the name of the function is
36
+ # the second argument (now at index 0 since we deleted the command)
37
+ # and the rest of the arguments are passed to the function
38
+ call ARGV[0], ARGV[1..-1]
39
+ when 'test'
40
+ NRSER::Rash._test_function ARGV.shift #, ARGV[1, ARGV.length]
41
+ when 'list'
42
+ list
43
+ when 'help', '-h', '--help', nil
44
+ help
45
+ # Exit with error so these calls don't work with `&&` in shell
46
+ exit 1
47
+ when '--version', 'version', '-v'
48
+ puts NRSER::Rash::VERSION
49
+ else
50
+ call command, ARGV
51
+ end
52
+ end
53
+
54
+ end # module NRSER::Rash::CLI
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Definitions
5
+ # =======================================================================
6
+
7
+ # Where the CLI commands live as class methods.
8
+ #
9
+ module NRSER::Rash::CLI
10
+
11
+ include SemanticLogger::Loggable
12
+
13
+ end # module NRSER::Rash::CLI
14
+
15
+
16
+ # Post-Processing
17
+ # =======================================================================
18
+ require_relative './cli/call'
19
+ require_relative './cli/help'
20
+ require_relative './cli/list'
21
+ require_relative './cli/run'
@@ -0,0 +1,172 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Stdlib
8
+ # -----------------------------------------------------------------------
9
+
10
+ # Deps
11
+ # -----------------------------------------------------------------------
12
+
13
+ # Project / Package
14
+ # -----------------------------------------------------------------------
15
+
16
+
17
+ # Refinements
18
+ # =======================================================================
19
+
20
+ using NRSER
21
+ using NRSER::Types
22
+
23
+
24
+ # Definitions
25
+ # =======================================================================
26
+
27
+ # @todo document NRSER::Rash module.
28
+ module NRSER::Rash
29
+
30
+ COMMAND_LINE_CONFIG = {}
31
+
32
+
33
+ # Get a config value by name. First looks for an env var defined with
34
+ # an added `'RASH_'` prefix, then for a const in the {NRSER::Rash}
35
+ # module with the provided name.
36
+ #
37
+ # @todo This example is out of date...
38
+ #
39
+ # Easiest to explain by example:
40
+ #
41
+ # NRSER::Rash::config('BLAH')
42
+ #
43
+ # will first see if there is an env var named
44
+ #
45
+ # 'RASH_BLAH'
46
+ #
47
+ # defined, and return it's value if so.
48
+ #
49
+ # Otherwise, it will try and return
50
+ #
51
+ # NRSER::RASH::Config::BLAH
52
+ #
53
+ # raising an exception if it's not found.
54
+ #
55
+ # Defined here so it can be used when defining the
56
+ # `NRSER::Rash::Config::<name>` consts.
57
+ #
58
+ def self.config name, *default
59
+ if default.length > 1
60
+ raise ArgumentError,
61
+ "wrong number of arguments" \
62
+ "(given #{ 1 + default.length }, expected 1-2)"
63
+ end
64
+
65
+ # command line config options take precedent (those that start with
66
+ # '--RASH_', which are never passed to the function)
67
+ if COMMAND_LINE_CONFIG.key? name
68
+ COMMAND_LINE_CONFIG[name]
69
+ elsif ENV.key? "RASH_#{ name }"
70
+ ENV["RASH_#{ name }"]
71
+ else
72
+ begin
73
+ Config.const_get(name)
74
+ rescue NameError => e
75
+ if default.empty?
76
+ raise
77
+ else
78
+ default[0]
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ # Where the default config options are set.
86
+ #
87
+ # These are overridden by:
88
+ #
89
+ # 1. By command line options of the form
90
+ #
91
+ # --RASH_<name>
92
+ #
93
+ # where `<name>` corresponds to the `NRSER::Rash::Config::<name>`
94
+ # constant.
95
+ #
96
+ # 2. By environment variables of the form
97
+ #
98
+ # RASH_<name>
99
+ #
100
+ # where `<name>` corresponds to the `NRSER::Rash::Config.<name>`
101
+ # constant.
102
+ #
103
+ # Don't access these directly, use {NRSER::Rash.config} to access in
104
+ # proper precedence.
105
+ #
106
+ module Config
107
+ # Where to look for Rash files
108
+ HOME = ENV['HOME']
109
+
110
+ # Where to look for the Rash profile file
111
+ PROFILE = "#{ NRSER::Rash.config('HOME') }/profile.rb"
112
+
113
+ # Where to look for the Rash functions file
114
+ FUNCTIONS = "#{ NRSER::Rash.config('HOME') }/functions.rb"
115
+
116
+ # The default {NRSER::Rash::Formatter} to use
117
+ DEFAULT_FORMATTER = 'pp'
118
+
119
+ # Logging level
120
+ LOG_LEVEL = :info
121
+
122
+ # Print stacktrace on top-level function error
123
+ STACKTRACE = 'false'
124
+
125
+ # Don't print errors
126
+ PRINT_ERRORS = 'true'
127
+
128
+ # Stub function namespace separator
129
+ STUB_NAMESPACE_SEPERATOR = '__'
130
+ end
131
+
132
+ # Rash's project dir
133
+ ROOT_DIR = File.expand_path(File.dirname(__FILE__) + '/..')
134
+
135
+ # Executable
136
+ EXECUTABLE = ROOT_DIR + '/bin/rash'
137
+
138
+ # Proxy to {NRSER.truthy?}
139
+ #
140
+ def self.truthy? string
141
+ NRSER.truthy? string
142
+ end
143
+
144
+ # Old name without `?`
145
+ singleton_class.send :alias_method, :truthy, :truthy?
146
+
147
+
148
+ # Proxy to {NRSER.falsy?}
149
+ #
150
+ def self.falsy? string
151
+ return string.falsy?
152
+ end
153
+
154
+ # Old name
155
+ singleton_class.send :alias_method, :falsey, :falsy?
156
+
157
+
158
+ def self.filter_and_set_config(args)
159
+ args.reject! do |arg|
160
+ # long options of form '--<name>=<value>'
161
+ if m = arg.match(/\A--RASH_([^=]*)(=?)(.*)/)
162
+ COMMAND_LINE_CONFIG[m[1]] = if m[2] == ''
163
+ true
164
+ else
165
+ m[3]
166
+ end
167
+ true
168
+ end
169
+ end
170
+ end
171
+
172
+ end # module NRSER::Rash
@@ -0,0 +1,55 @@
1
+ class Object
2
+ # Monkey-patch to add an instance variable named `rash_formatter`
3
+ # to `Object` to control how that object is formatted for printing if
4
+ # it's the final value returned to {NRSER::Rash::CLI.call}.
5
+ #
6
+ # The value should either be a symbol identifying a method on
7
+ # {NRSER::Rash::Formatters} to call or a callable that takes the object
8
+ # as it's only argument and returns a formatted `String`.
9
+ #
10
+ attr_accessor :rash_formatter
11
+
12
+ # shortcut method to set the `rash_formatter` and return the object.
13
+ # this method is hacked up a bit to support the following syntax:
14
+ #
15
+ # {:x => 1, :y => 2}.rash_formatter_tap :json
16
+ #
17
+ # {:x => 1, :y => 2}.rash_formatter_tap do |obj|
18
+ # "here's the object: #{ obj.inspect }"
19
+ # end
20
+ #
21
+ # which returns the object itself, allowing it to easily be tacked on to
22
+ # the last statement in a method. it's equivalent to:
23
+ #
24
+ # {:x => 1, :y => 2}.tap {|obj| obj.rash_formatter = :json}
25
+ #
26
+ # {:x => 1, :y => 2}.tap do |obj|
27
+ # obj.rash_formatter = Proc.new do |obj|
28
+ # "here's the object: #{ obj.inpsect }"
29
+ # end
30
+ # end
31
+ #
32
+ def rash_formatter_tap(formatter = (default = true; nil), &block)
33
+ if default
34
+ if block.nil?
35
+ # neither the `formatter` arg or a block were supplied, so
36
+ # the method is acting as an attribute reader, return the
37
+ # value
38
+ raise ArgumentError.new "must provide an argument or block."
39
+ else
40
+ # only a block was supplied, set it as the formatter and
41
+ # return the object
42
+ @rash_formatter = block
43
+ self
44
+ end
45
+ else
46
+ if block
47
+ raise ArgumentError.new "can't provide an argument and block."
48
+ else
49
+ # only `formatter` arg was supplied, set it and return the object
50
+ @rash_formatter = formatter
51
+ self
52
+ end
53
+ end
54
+ end
55
+ end # class Object
@@ -0,0 +1,105 @@
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
+ # these are functions mapping an `Object` to a `String` for printing.
15
+ # their names can be used as values for `Object.rash_formatter` to indicate
16
+ # Rash should use that function to print the object.
17
+ #
18
+ module NRSER::Rash::Formatters
19
+ def self.yaml(obj)
20
+ require 'yaml'
21
+ YAML.dump(obj)
22
+ end
23
+
24
+ # print a white-space formatted key/value table given a hash.
25
+ # keys and values are printed cast to strings, and nested
26
+ # hashes are treated as sub-keys:
27
+ #
28
+ # NRSER::Rash::Formatters.kv({
29
+ # :scheme => "https",
30
+ # :host => "www.google.com",
31
+ # :port => 443,
32
+ # :params => {
33
+ # :q => "blah",
34
+ # :oq => "blah",
35
+ # :aqs => "chrome.0.57j60l4j59.468",
36
+ # :sourceid => "chrome",
37
+ # :ie => "UTF-8"
38
+ # },
39
+ # :fragment => nil
40
+ # })
41
+ #
42
+ # scheme: https
43
+ # host: www.google.com
44
+ # port: 443
45
+ # params:
46
+ # q: blah
47
+ # oq: blah
48
+ # aqs: chrome.0.57j60l4j59.468
49
+ # sourceid: chrome
50
+ # ie: UTF-8
51
+ # fragment:
52
+ #
53
+ def self.kv(hash)
54
+ find_width = lambda do |hash, indent|
55
+ width = 0
56
+ hash.each do |key, value|
57
+ this_width = if value.is_a? Hash
58
+ find_width.call(value, indent + 2)
59
+ else
60
+ key.to_s.length + indent
61
+ end
62
+ if this_width > width
63
+ width = this_width
64
+ end
65
+ end
66
+ width
67
+ end
68
+ width = find_width.call(hash, 0)
69
+ out = ''
70
+ write_out = lambda do |hash, indent|
71
+ hash.each do |key, value|
72
+ out << ' ' * indent \
73
+ << "#{ key }: " \
74
+ << ' ' * (width - key.to_s.length - indent)
75
+ if value.is_a? Hash
76
+ out << "\n"
77
+ write_out.call(value, indent + 2)
78
+ else
79
+ out << "#{ value }\n"
80
+ end
81
+ end
82
+ end
83
+ write_out.call(hash, 0)
84
+ out
85
+ end
86
+
87
+ def self.json(obj)
88
+ require 'json'
89
+ JSON.dump(obj)
90
+ end
91
+
92
+ def self.json_pp(obj)
93
+ require 'json'
94
+ JSON.pretty_generate(obj)
95
+ end
96
+
97
+ def self.pp(obj)
98
+ require 'pp'
99
+ require 'stringio'
100
+ sio = StringIO.new
101
+ PP.pp(obj, sio)
102
+ sio.string
103
+ end
104
+
105
+ end # module NRSER::Rash::Formatters