rexe 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +161 -49
  4. data/exe/rexe +75 -31
  5. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89d0c2488db063c926dcbdcc71311538e0aa6a6cea5508174adbb251b1583cd0
4
- data.tar.gz: 86f099c8ef308584df094492a5e64765e3b26d3d7fc872442ec05e7963f578b0
3
+ metadata.gz: facbd7a97a0fba70fb40170f61f53a127b6cb1ec05d44aed85a653a97b711d88
4
+ data.tar.gz: f742717a826d26f719485bf7cb562e598d1c3130ca3abe4065c2bd1bc90c34c0
5
5
  SHA512:
6
- metadata.gz: 8fd605f513730ec939e57a6ccc0c3b21974af416f6c0c7c1bcb033a9ed421375eecb55a6a871017dac2cffa2edc6c150c018e3532f88725bbff31505efdd59dc
7
- data.tar.gz: 2cd444bb1b7300ac7d41238335c5c62432584b13474f87f3134f3bcd06be6d5304c81a69bbe9b278a249283fd932fa07cc2d11d8bacc18d6044a03c6fdc39b6d
6
+ metadata.gz: 819cf812598dfb9612310a71c4b9bb5c8fa00442e16650b9cb8441b2e21a36e39a9e1ead336ca4be9451edf26edf9599f14b1c75ac5935fd069535118f5d78c2
7
+ data.tar.gz: fe8998aab47ae22b8082439305b28f719296117f183cf49e632ecfe6192cb02bcc99a2536ba9b7b37361642c27619c0cff7d92bae14cd5a8f2a0017369aaab6d
@@ -1,5 +1,22 @@
1
1
  ## rexe -- Ruby Command Line Executor
2
2
 
3
+
4
+ ### v0.2.0
5
+
6
+ * Improve README and verbose logging.
7
+
8
+ ### v0.1.0
9
+
10
+ * Add ability to handle input as a single multiline string (using -mb option).
11
+ * Add -mn mode for no input at all.
12
+ * Fix and improve usage examples in README.
13
+
14
+
15
+ ### v0.0.2
16
+
17
+ * Fix running-as-script test.
18
+
19
+
3
20
  ### v0.0.1
4
21
 
5
22
  * Initial version.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Rexe
2
2
 
3
+ A configurable Ruby command line filter/executor.
4
+
5
+
3
6
 
4
7
  ## Installation
5
8
 
@@ -16,85 +19,194 @@ chmod +x rexe
16
19
 
17
20
  ## Usage
18
21
 
22
+ rexe is a _filter_ in that it can consume standard input and emit standard output; but it is also an _executor_, meaning it can be used without either standard input or output.
23
+
24
+ ### Help Text
25
+
26
+ As a summary, here is the help text printed out by the application:
27
+
19
28
  ```
20
29
 
21
- rexe -- Ruby Command Line Filter -- v0.0.1 -- https://github.com/keithrbennett/rexe
22
30
 
23
- Takes standard input and runs the specified code on it, sending the result to standard output.
24
- Your Ruby code can operate on each line individually (-ms) (the default),
25
- or operate on the enumerator of all lines (-me). If the latter, you will probably need to
26
- call chomp on the lines yourself to remove the trailing newlines.
31
+ rexe -- Ruby Command Line Filter -- v1.1.0 -- https://github.com/keithrbennett/rexe
32
+
33
+ Optionally takes standard input and runs the specified code on it, sending the result to standard output.
27
34
 
28
35
  Options:
29
36
 
30
37
  -h, --help Print help and exit
31
- -m, --mode MODE Mode with which to handle input, (-ms for string (default), -me for enumerator)
38
+ -l, --load A_RUBY_FILE Load this Ruby source code file
39
+ -m, --mode MODE Mode with which to handle input (i.e. what `self` will be in the code):
40
+ -ms for each line to be handled separately as a string (default)
41
+ -me for an enumerator of lines (least memory consumption for big data)
42
+ -mb for 1 big string (all lines combined into single multiline string)
43
+ -mn to execute the specified Ruby code on no input at all
32
44
  -r, --require REQUIRES Gems and built-in libraries (e.g. shellwords, yaml) to require, comma separated
33
45
  -v, --[no-]verbose Verbose mode, writes to stderr
34
46
 
35
- If there is an .rexerc file in your home directory, it will be run as Ruby code before processing the input.
47
+ If there is an .rexerc file in your home directory, it will be run as Ruby code
48
+ before processing the input.
36
49
 
37
50
  If there is an REXE_OPTIONS environment variable, its content will be prepended to the command line
38
51
  so that you can specify options implicitly (e.g. `export REXE_OPTIONS="-r awesome_print,yaml"`)
39
52
 
40
53
  ```
41
- ## License
42
54
 
43
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
55
+ ### Input Mode
56
+
57
+ When it is used as a filter, the input is accessed differently in the source code depending on the mode that was specified (see example section below for examples). The mode letter is appended to `-m` on the command line; `s` (_string_) mode is the default.
58
+
59
+ * `s` - _string_ mode - the source code is run once on each line of input, and `self` is each line of text
60
+ * `e` - _enumerator_ mode - the code is run once on the enumerator of all lines; `self` is the enumerator, so you can call `map`, `to_a`, `select`, etc without explicitly specifying `self`.
61
+ * `b` - _big string_ mode - all input is treated as one large (probably) multiline string with the newlines intact; `self` is this large string; this mode is required, for example, for parsing the text into a single object from JSON or YAML formatted data.
62
+ * `n` - _no input_ mode - this instructs the program to proceed without looking for input
63
+
64
+
65
+ ### Requires
66
+
67
+ As with the Ruby interpreter itself, `require`s can be specified on the command line with the `-r` option. Multiple requires can be combined with commas between them.
68
+
69
+
70
+ ### Loading Ruby Files
71
+
72
+ Other Ruby files can be loaded by `rexe`, to, for example, define classes and methods to use, set up resources, etc. They are specified with the `-l` option.
73
+
74
+ If there is a file named `.rexerc` in the home directory, that will always be loaded without explicitly requesting it on the command line.
75
+
76
+
77
+ ### Verbose Mode
78
+
79
+ Verbose mode outputs information to standard error (stderr). This information can be redirected, for example to a file named `rexe.log`, by adding `2>& rexe.log` to the command line.
80
+
81
+ Here is an example of some text that might be output in verbose mode:
82
+
83
+ ```
84
+ rexe version 1.1.0 -- 2019-02-04 21:50:34 +0700
85
+ Source Code: sort.to_a.first(3).ai
86
+ Requiring awesome_print
87
+ Loading global config file /Users/kbennett/.rexerc
88
+ ```
89
+
90
+ ### The REXE_OPTIONS Environment Variable
91
+
92
+ Very often you will want to call `rexe` several times with similar options. Instead of having to clutter the command line each time with these options, you can put them in an environment variable named `REXE_OPTIONS`, and they will be prepended automatically. Since they will be processed before the options on the command line, they are of lower precedence and can be overridden.
93
+
44
94
 
95
+ ## Troubleshooting
96
+
97
+ One common problem relates to the shell's special handling of characters. Remember that the shell will process special characters, thereby changing your text before passing it on to the Ruby code. It is good to get in the habit of putting your source code in double quotes; and if the source code itself uses quotes, use `q{}` or `Q{}` instead. For example:
98
+
99
+ ```
100
+ ➜  rexe -mn "puts %Q{The time is now #{Time.now}}"
101
+ The time is now 2019-02-04 18:49:31 +0700
102
+ ```
103
+
104
+ If you are troubleshooting the setup (i.e. the command line options, loaded files, and `REXE_OPTIONS` environment variable) using the verbose option, you may have the problem of the logging scrolling off the screen due to the length of your output. In this case you could easily fake or disable the output adding `; nil`, `; 'foo'`', etc. to the end of your expression. This way you don't have to mess with your code's logic.
45
105
 
46
106
  ## Examples
47
107
 
48
108
  ```
49
- ➜ rexe git:(master) ✗  ls | exe/rexe "map(&:reverse).to_s"
50
- ["\nelifmeG", "\ntxt.ESNECIL", "\ndm.EMDAER", "\nelifekaR", "\nnib", "\nexe", "\nbil", "\ncepsmeg.cbr", "\nceps"]
109
+ # Call reverse on listed file.
110
+ # No need to specify the mode, since it defaults to "s" ("-ms"),
111
+ # which treats every line separately.
112
+ ➜ rexe git:(master) ✗  ls | head -2 | exe/rexe "self + ' --> ' + reverse"
113
+ CHANGELOG.md --> dm.GOLEGNAHC
114
+ Gemfile --> elifmeG
51
115
 
52
- ➜ rexe git:(master) ✗  uptime | exe/rexe -l split.first
53
- 20:51
116
+ ----
54
117
 
118
+ # Use input data to create a human friendly message:
119
+ ➜ ~  uptime | rexe "%Q{System has been up: #{split.first}.}"
120
+ System has been up: 17:10.
55
121
 
56
- ➜ rexe git:(master) ✗  ls | exe/rexe -r json "to_a.to_json"
57
- ["Gemfile\n","LICENSE.txt\n","README.md\n","Rakefile\n","bin\n","exe\n","lib\n","rexe.gemspec\n","spec\n"]
122
+ ----
58
123
 
124
+ # Create a JSON array of a file listing.
125
+ # Use the "-me" flag so that all input is treated as a single enumerator of lines.
126
+ ➜ /etc  ls | head -3 | rexe -me -r json "map(&:strip).to_a.to_json"
127
+ ["AFP.conf","afpovertcp.cfg","afpovertcp.cfg~orig"]
59
128
 
60
- ➜ rexe git:(master) ✗  ls | exe/rexe -r yaml "map(&:chomp).to_a.to_yaml"
61
- ---
62
- - Gemfile
63
- - LICENSE.txt
64
- - README.md
65
- - Rakefile
66
- - bin
67
- - exe
68
- - lib
69
- - rexe.gemspec
70
- - spec
129
+ ----
71
130
 
131
+ # Create a "pretty" JSON array of a file listing:
132
+ ➜ /etc  ls | head -3 | rexe -me -r json "JSON.pretty_generate(map(&:strip).to_a)"
133
+ [
134
+ "AFP.conf",
135
+ "afpovertcp.cfg",
136
+ "afpovertcp.cfg~orig"
137
+ ]
72
138
 
73
- ➜ rexe git:(master) ✗  export REXE_OPTIONS="-r yaml,awesome_print"
139
+ ----
74
140
 
75
- ➜ rexe git:(master) ✗  ls | exe/rexe "map(&:chomp).to_a.to_yaml"
141
+ # Create a YAML array of a file listing:
142
+ ➜ /etc  ls | head -3 | rexe -me -r yaml "map(&:strip).to_a.to_yaml"
76
143
  ---
77
- - Gemfile
78
- - LICENSE.txt
79
- - README.md
80
- - Rakefile
81
- - bin
82
- - exe
83
- - lib
84
- - rexe.gemspec
85
- - spec
86
-
87
- ➜ rexe git:(master) ✗  ls | exe/rexe "map(&:chomp).to_a.ai"
144
+ - AFP.conf
145
+ - afpovertcp.cfg
146
+ - afpovertcp.cfg~orig
147
+
148
+ ----
149
+
150
+ # Use AwesomePrint to print a file listing.
151
+ # (Rather than calling the `ap` method on the object to print,
152
+ # call the `ai` method _on_ the object to print:
153
+ ➜ /etc  ls | head -3 | rexe -me -r awesome_print "map(&:chomp).ai"
88
154
  [
89
- [0] "Gemfile",
90
- [1] "LICENSE.txt",
91
- [2] "README.md",
92
- [3] "Rakefile",
93
- [4] "bin",
94
- [5] "exe",
95
- [6] "lib",
96
- [7] "rexe.gemspec",
97
- [8] "spec"
155
+ [0] "AFP.conf",
156
+ [1] "afpovertcp.cfg",
157
+ [2] "afpovertcp.cfg~orig"
98
158
  ]
99
159
 
100
- ```
160
+ ----
161
+
162
+ # Don't use input at all, so use "-mn" to tell rexe not to expect input.
163
+ ➜ /etc  rexe -mn "%Q{The time is now #{Time.now}}"
164
+ The time is now 2019-02-04 17:20:03 +0700
165
+
166
+ ----
167
+
168
+ # Use REXE_OPTIONS environment variable to eliminate the need to specify
169
+ # options on each invocation:
170
+
171
+ # First it will fail since these symbols have not been loaded via require:
172
+ ➜ /etc  rexe -mn "[JSON, YAML, AwesomePrint]"
173
+ Traceback (most recent call last):
174
+ ...
175
+ (eval):1:in `block in call': uninitialized constant Rexe::JSON (NameError)
176
+
177
+ # Now we specify the requires in the REXE_OPTIONS environment variable.
178
+ # Contents of this variable will be prepended to the arguments
179
+ # specified on the command line.
180
+ ➜ /etc  export REXE_OPTIONS="-r json,yaml,awesome_print"
181
+
182
+ # Now that command that previously failed will succeed:
183
+ ➜ /etc  rexe -mn "[JSON, YAML, AwesomePrint].to_s"
184
+ [JSON, Psych, AwesomePrint]
185
+
186
+ ----
187
+
188
+ Access public JSON data and print it with awesome_print:
189
+
190
+ ➜ /etc  curl https://data.lacity.org/api/views/nxs9-385f/rows.json\?accessType\=DOWNLOAD \
191
+ | rexe -mb -r awesome_print,json "JSON.parse(self).ai"
192
+ {
193
+ "meta" => {
194
+ "view" => {
195
+ "id" => "nxs9-385f",
196
+ "name" => "2010 Census Populations by Zip Code",
197
+ ...
198
+
199
+ ----
200
+
201
+ # Print the environment variables, sorted, with Awesome Print:
202
+ ➜ /etc  env | rexe -me -r awesome_print sort.to_a.ai
203
+ [
204
+ ...
205
+ [ 4] "COLORFGBG=15;0\n",
206
+ [ 5] "COLORTERM=truecolor\n",
207
+ ...
208
+ ```
209
+
210
+ ## License
211
+
212
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/exe/rexe CHANGED
@@ -1,20 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
2
  #
3
- # rexe - Ruby Command Line Filter
3
+ # rexe - Ruby Command Line Executor Filter
4
4
  #
5
5
  # Inspired by https://github.com/thisredone/rb
6
- #
6
+
7
7
 
8
8
  require 'optparse'
9
9
  require 'shellwords'
10
10
 
11
+ # Rexe - Ruby Executor
12
+ class Rexe < Struct.new(:input_mode, :loads, :requires, :verbose)
11
13
 
12
- class Rexe < Struct.new(:line_mode, :requires, :verbose)
13
-
14
- VERSION = '0.0.1'
14
+ VERSION = '0.2.0'
15
15
 
16
16
  def initialize
17
- self.line_mode = :string
17
+ self.input_mode = :string
18
+ self.loads = []
18
19
  self.requires = []
19
20
  self.verbose = false
20
21
  end
@@ -25,19 +26,22 @@ class Rexe < Struct.new(:line_mode, :requires, :verbose)
25
26
 
26
27
  rexe -- Ruby Command Line Filter -- v#{VERSION} -- https://github.com/keithrbennett/rexe
27
28
 
28
- Takes standard input and runs the specified code on it, sending the result to standard output.
29
- Your Ruby code can operate on each line individually (-ms) (the default),
30
- or operate on the enumerator of all lines (-me). If the latter, you will probably need to
31
- call chomp on the lines yourself to remove the trailing newlines.
29
+ Optionally takes standard input and runs the specified code on it, sending the result to standard output.
32
30
 
33
31
  Options:
34
32
 
35
33
  -h, --help Print help and exit
36
- -m, --mode MODE Mode with which to handle input, (-ms for string (default), -me for enumerator)
34
+ -l, --load A_RUBY_FILE Load this Ruby source code file
35
+ -m, --mode MODE Mode with which to handle input (i.e. what `self` will be in the code):
36
+ -ms for each line to be handled separately as a string (default)
37
+ -me for an enumerator of lines (least memory consumption for big data)
38
+ -mb for 1 big string (all lines combined into single multiline string)
39
+ -mn to execute the specified Ruby code on no input at all
37
40
  -r, --require REQUIRES Gems and built-in libraries (e.g. shellwords, yaml) to require, comma separated
38
41
  -v, --[no-]verbose Verbose mode, writes to stderr
39
42
 
40
- If there is an .rexerc file in your home directory, it will be run as Ruby code before processing the input.
43
+ If there is an .rexerc file in your home directory, it will be run as Ruby code
44
+ before processing the input.
41
45
 
42
46
  If there is an REXE_OPTIONS environment variable, its content will be prepended to the command line
43
47
  so that you can specify options implicitly (e.g. `export REXE_OPTIONS="-r awesome_print,yaml"`)
@@ -65,17 +69,25 @@ class Rexe < Struct.new(:line_mode, :requires, :verbose)
65
69
  exit
66
70
  end
67
71
 
72
+ parser.on('-l', '--load RUBY_FILE', 'Loads and runs this Ruby file') do |v|
73
+ self.loads << v
74
+ end
75
+
68
76
  parser.on('-m', '--mode MODE',
69
77
  'Mode with which to handle input (-ms for string (default), -me for enumerator)') do |v|
70
- self.line_mode = case v
71
- when 's'
72
- self.line_mode = :string
73
- when 'e'
74
- self.line_mode = :enumerator
75
- else
76
- puts "Mode must be either 's' for string or 'e' for enumerator"
77
- exit -1
78
- end
78
+
79
+ modes = {
80
+ 's' => :string,
81
+ 'e' => :enumerator,
82
+ 'b' => :one_big_string,
83
+ 'n' => :no_input
84
+ }
85
+
86
+ self.input_mode = modes[v]
87
+ if self.input_mode.nil?
88
+ puts help_text
89
+ raise "Input mode must be one of #{modes.keys}."
90
+ end
79
91
  end
80
92
 
81
93
  parser.on('-r', '--require REQUIRES', 'Gems and modules to require, comma separated') do |v|
@@ -92,13 +104,20 @@ class Rexe < Struct.new(:line_mode, :requires, :verbose)
92
104
  def load_global_config_if_exists
93
105
  filespec = File.join(Dir.home, '.rexerc')
94
106
  exists = File.exists?(filespec)
95
- load(filespec) if exists
107
+ if exists
108
+ log_if_verbose("Loading global config file #{filespec}")
109
+ load(filespec)
110
+ end
96
111
  exists ? filespec : nil
97
112
  end
98
113
 
99
114
 
100
- def execute(line_or_enumerator, code)
101
- puts line_or_enumerator.instance_eval(&code)
115
+ def execute(eval_context_object, code)
116
+ if eval_context_object
117
+ puts eval_context_object.instance_eval(&code)
118
+ else
119
+ puts code.call
120
+ end
102
121
  rescue Errno::EPIPE
103
122
  exit(-13)
104
123
  end
@@ -110,21 +129,46 @@ class Rexe < Struct.new(:line_mode, :requires, :verbose)
110
129
 
111
130
 
112
131
  def call
132
+ start_time = Time.now
133
+
113
134
  parse_command_line
114
135
 
115
- log_if_verbose("Requiring #{requires}") if requires.any?
116
- requires.each { |r| require r }
136
+ log_if_verbose("rexe version #{VERSION} -- #{Time.now}")
137
+ log_if_verbose('Source Code: ' + ARGV.join(' '))
117
138
 
118
- filespec = load_global_config_if_exists
119
- log_if_verbose("Loaded #{filespec}") if filespec
139
+ requires.each do |r|
140
+ log_if_verbose("Requiring #{r}")
141
+ require(r)
142
+ end
143
+
144
+ load_global_config_if_exists
145
+
146
+ loads.each do |file|
147
+ log_if_verbose("Loading #{file}")
148
+ load(file)
149
+ end
120
150
 
121
151
  source_code = "Proc.new { #{ARGV.join(' ')} }"
122
- log_if_verbose("Source code: #{source_code}")
123
152
  code = eval(source_code)
124
153
 
125
- (line_mode == :string) ? STDIN.each { |l| execute(l.chomp, code) } : execute(STDIN.each_line, code)
154
+
155
+ actions = {
156
+ string: -> { STDIN.each { |l| execute(l.chomp, code) } },
157
+ enumerator: -> { execute(STDIN.each_line, code) },
158
+ one_big_string: -> { big_string = STDIN.each_line.to_a.join; execute(big_string, code) },
159
+ no_input: -> { execute(nil, code) }
160
+ }
161
+
162
+ actions[input_mode].()
163
+
164
+ duration = Time.now - start_time
165
+ log_if_verbose("rexe time elapsed: #{duration} seconds.")
126
166
  end
127
167
  end
128
168
 
129
- Rexe.new.call if $0 == __FILE__
169
+ # This is needed because the gemspec file loads this file to access Rexe::VERSION
170
+ # and must not have it run at that time:
171
+ called_as_script = (File.basename($0) == File.basename(__FILE__))
172
+ Rexe.new.call if called_as_script
173
+
130
174
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rexe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Bennett
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-03 00:00:00.000000000 Z
11
+ date: 2019-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -97,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
97
  - !ruby/object:Gem::Version
98
98
  version: '0'
99
99
  requirements: []
100
- rubygems_version: 3.0.1
100
+ rubygems_version: 3.0.2
101
101
  signing_key:
102
102
  specification_version: 4
103
103
  summary: Ruby Command Line Executor