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,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