ing 0.1.1
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.
- data/.gitignore +5 -0
- data/GENERATORS.md +2 -0
- data/LICENSE +18 -0
- data/OPTIONS.md +2 -0
- data/README.md +251 -0
- data/TASKS.md +21 -0
- data/bin/ing +5 -0
- data/examples/rspec_convert.rb +102 -0
- data/ing.gemspec +29 -0
- data/ing.rb +102 -0
- data/lib/ing.rb +78 -0
- data/lib/ing/actions/create_file.rb +105 -0
- data/lib/ing/actions/create_link.rb +57 -0
- data/lib/ing/actions/directory.rb +98 -0
- data/lib/ing/actions/empty_directory.rb +155 -0
- data/lib/ing/actions/file_manipulation.rb +308 -0
- data/lib/ing/actions/inject_into_file.rb +109 -0
- data/lib/ing/commands/boot.rb +76 -0
- data/lib/ing/commands/generate.rb +64 -0
- data/lib/ing/commands/help.rb +87 -0
- data/lib/ing/commands/implicit.rb +59 -0
- data/lib/ing/commands/list.rb +108 -0
- data/lib/ing/dispatcher.rb +132 -0
- data/lib/ing/files.rb +190 -0
- data/lib/ing/lib_trollop.rb +782 -0
- data/lib/ing/shell.rb +390 -0
- data/lib/ing/trollop/parser.rb +17 -0
- data/lib/ing/util.rb +61 -0
- data/lib/ing/version.rb +3 -0
- data/lib/thor/actions/file_manipulation.rb +30 -0
- data/lib/thor/shell/basic.rb +44 -0
- data/test/acceptance/ing_run_tests.rb +164 -0
- data/test/actions/create_file_spec.rb +209 -0
- data/test/actions/create_link_spec.rb +90 -0
- data/test/actions/directory_spec.rb +167 -0
- data/test/actions/empty_directory_spec.rb +146 -0
- data/test/actions/file_manipulation_spec.rb +433 -0
- data/test/actions/inject_into_file_spec.rb +147 -0
- data/test/fixtures/application.rb +2 -0
- data/test/fixtures/app{1}/README +3 -0
- data/test/fixtures/bundle/execute.rb +6 -0
- data/test/fixtures/bundle/main.thor +1 -0
- data/test/fixtures/doc/%file_name%.rb.tt +1 -0
- data/test/fixtures/doc/COMMENTER +10 -0
- data/test/fixtures/doc/README +3 -0
- data/test/fixtures/doc/block_helper.rb +3 -0
- data/test/fixtures/doc/components/.empty_directory +0 -0
- data/test/fixtures/doc/config.rb +1 -0
- data/test/fixtures/doc/config.yaml.tt +1 -0
- data/test/fixtures/group.ing.rb +76 -0
- data/test/fixtures/invok.ing.rb +50 -0
- data/test/fixtures/namespace.ing.rb +52 -0
- data/test/fixtures/require.ing.rb +7 -0
- data/test/fixtures/task.ing.rb +36 -0
- data/test/fixtures/task.thor +10 -0
- data/test/spec_helper.rb +2 -0
- data/test/test_helper.rb +41 -0
- data/todo.yml +7 -0
- metadata +147 -0
data/.gitignore
ADDED
data/GENERATORS.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2012 Eric Gjertsen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/OPTIONS.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
# Ing
|
2
|
+
## Vanilla ruby command-line scripting.
|
3
|
+
|
4
|
+
or gratuitous backronym: <b>I</b> <b>N</b>eed a <b>G</b>enerator!
|
5
|
+
|
6
|
+
Note this is a work-in-progress, not quite ready for use.
|
7
|
+
|
8
|
+
The command-line syntax is similar to Thor's, and it incorporates Thor's
|
9
|
+
(Rails') generator methods and shell conventions like
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
if yes? 'process foo files?', :yellow
|
13
|
+
inside('foo') { create_file '%foo_file%.rb' }
|
14
|
+
end
|
15
|
+
```
|
16
|
+
but _unlike_ Thor or Rake, it does not define its own DSL. Your tasks correspond
|
17
|
+
to plain ruby classes and methods. Ing just handles routing from the command line
|
18
|
+
to them, and setting options. Your classes (or even Procs) do the rest.
|
19
|
+
|
20
|
+
Option parsing courtesy of the venerable and excellent
|
21
|
+
[Trollop](http://trollop.rubyforge.org/), under the hood.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
_Note: not yet gemified_
|
26
|
+
|
27
|
+
gem install ing
|
28
|
+
|
29
|
+
## A quick tour
|
30
|
+
|
31
|
+
### The command line
|
32
|
+
|
33
|
+
The ing command line is generally parsed as
|
34
|
+
|
35
|
+
[ing command] [ing command options] [subcommand] [args] [subcommand options]
|
36
|
+
|
37
|
+
But in cases where the first argument isn't an built-in ing command or options, it's
|
38
|
+
simplified to
|
39
|
+
|
40
|
+
[subcommand] [args] [subcommand options]
|
41
|
+
|
42
|
+
The "subcommand" is your task. To take some examples.
|
43
|
+
|
44
|
+
ing -r./path/to/some/task.rb some:task run something --verbose
|
45
|
+
|
46
|
+
1. `ing -r` loads specified ruby files or libraries/gems; then
|
47
|
+
2. it dispatches to `Some::Task.new(:verbose => true).run("something")`.
|
48
|
+
|
49
|
+
(Assuming you define a task `Some::Task#run`, in `/path/to/some/task.rb`.)
|
50
|
+
|
51
|
+
You can -r as many libaries/files as you like. Of course, that gets pretty
|
52
|
+
long-winded.
|
53
|
+
|
54
|
+
By default, it requires a file `./ing.rb` if it exists (the equivalent of
|
55
|
+
Rakefile or Thorfile). In which case, assuming your task class is
|
56
|
+
defined or loaded from there, the command can be simply
|
57
|
+
|
58
|
+
ing some:task run --verbose
|
59
|
+
|
60
|
+
### Built-in commands
|
61
|
+
|
62
|
+
Ing has some built-in commands. These are still being implemented, but
|
63
|
+
you can see what they are so far with `ing list`.
|
64
|
+
|
65
|
+
The most significant subcommand is `generate` or `g`, which
|
66
|
+
simplifies a common and familiar use-case (at the expense of some file-
|
67
|
+
system conventions):
|
68
|
+
|
69
|
+
ing generate some:task --force
|
70
|
+
|
71
|
+
Unlike Thor/Rails generators, these don't need to be packaged up as gems
|
72
|
+
and preloaded into ruby. They can be either parsed as:
|
73
|
+
|
74
|
+
1. A __file__ relative to a root dir: e.g. __some/task__, or
|
75
|
+
2. A __subdirectory__ of the root dir, in which case it attempts to
|
76
|
+
preload `ing.rb` within that subdirectory: e.g. __some/task/ing.rb__
|
77
|
+
|
78
|
+
The command above is then dispatched as normal to
|
79
|
+
`Some::Task.new(:force => true).call` (`#call` is used if no method is
|
80
|
+
specified). So you should put the task code within that namespace in the
|
81
|
+
preloaded file.
|
82
|
+
|
83
|
+
(By default, the generator root directory is specified by
|
84
|
+
`ENV['ING_GENERATORS_ROOT']` or failing that, `~/.ing/generators`.)
|
85
|
+
|
86
|
+
_TODO: more examples needed_
|
87
|
+
|
88
|
+
### A simple example of a plain old ruby task
|
89
|
+
|
90
|
+
Let's say you want to run your project's tests with a command like `ing test`.
|
91
|
+
The default is to run the whole suite; but if you just want unit tests you can
|
92
|
+
say `ing test unit`. This is what it would look like (in `./ing.rb`):
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class Test
|
96
|
+
|
97
|
+
# no options passed, but you need the constructor
|
98
|
+
def initialize(options); end
|
99
|
+
|
100
|
+
# `ing test`
|
101
|
+
def call(*args)
|
102
|
+
suite
|
103
|
+
end
|
104
|
+
|
105
|
+
# `ing test suite`
|
106
|
+
def suite
|
107
|
+
unit; functional; acceptance
|
108
|
+
end
|
109
|
+
|
110
|
+
# `ing test unit`
|
111
|
+
def unit
|
112
|
+
type 'unit'
|
113
|
+
end
|
114
|
+
|
115
|
+
# `ing test functional`
|
116
|
+
def functional
|
117
|
+
type 'functional'
|
118
|
+
end
|
119
|
+
|
120
|
+
# `ing test acceptance`
|
121
|
+
def acceptance
|
122
|
+
type 'acceptance'
|
123
|
+
end
|
124
|
+
|
125
|
+
def type(dir)
|
126
|
+
Dir["./test/#{dir}/*.rb"].each { |f| require_relative f }
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
As you can see, the second arg corresponds to the method name. `call` is what
|
133
|
+
gets called when there is no second arg. Organizing the methods like this means
|
134
|
+
you can also do `ing test type unit`: extra non-option arguments are passed into
|
135
|
+
the method as parameters.
|
136
|
+
|
137
|
+
For more worked examples of ing tasks, see the
|
138
|
+
[examples](ing/blob/master/examples) directory.
|
139
|
+
|
140
|
+
[MORE](ing/blob/master/TASKS.md)
|
141
|
+
|
142
|
+
### Option arguments
|
143
|
+
|
144
|
+
Your tasks (ing subcommands) can specify what options they take by defining a
|
145
|
+
class method `specify_options`. The best way to understand how this is done is
|
146
|
+
by example:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class Cleanup
|
150
|
+
|
151
|
+
def self.specify_options(expect)
|
152
|
+
expect.opt :quiet, "Run silently"
|
153
|
+
expect.opt :path, "Path to clean up", :type => :string, :default => '.'
|
154
|
+
end
|
155
|
+
|
156
|
+
attr_accessor :options
|
157
|
+
|
158
|
+
def initialize(options)
|
159
|
+
self.options = options
|
160
|
+
end
|
161
|
+
|
162
|
+
# ...
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
The syntax used in `self.specify_options` is Trollop - in fact what you are
|
167
|
+
doing is building a `Trollop::Parser` which then emits the parsed options into
|
168
|
+
your constructor. In general your constructor should just save the options to
|
169
|
+
an instance variable like this, but in some cases you might want to do further
|
170
|
+
processing of the passed options.
|
171
|
+
|
172
|
+
[MORE](ing/blob/master/OPTIONS.md)
|
173
|
+
|
174
|
+
### Generator tasks
|
175
|
+
|
176
|
+
If you want to use Thor-ish generator methods, your task classes need a few more
|
177
|
+
things added to their interface. Basically, it should look something like this.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class MyGenerator
|
181
|
+
|
182
|
+
def self.specify_options(expect)
|
183
|
+
# ...
|
184
|
+
end
|
185
|
+
|
186
|
+
include Ing::Files
|
187
|
+
|
188
|
+
attr_accessor :destination_root, :source_root, :options, :shell
|
189
|
+
|
190
|
+
# default == execution from within your project directory
|
191
|
+
def destination_root
|
192
|
+
@destination_root ||= Dir.pwd
|
193
|
+
end
|
194
|
+
|
195
|
+
# default == current file is within root directory of generator files
|
196
|
+
def source_root
|
197
|
+
@source_root ||= File.expand_path(File.dirname(__FILE__))
|
198
|
+
end
|
199
|
+
|
200
|
+
def initialize(options)
|
201
|
+
self.options = options
|
202
|
+
end
|
203
|
+
|
204
|
+
# ...
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
The generator methods need `:destination_root`, `:source_root`, and `:shell`.
|
209
|
+
Also, `include Ing::Files` _after_ you specify any options (this is because
|
210
|
+
`Ing::Files` adds several options automatically).
|
211
|
+
|
212
|
+
[MORE](ing/blob/master/GENERATORS.md)
|
213
|
+
|
214
|
+
## Motivation
|
215
|
+
|
216
|
+
I wanted to use Thor's generator methods and shell conventions to write my own
|
217
|
+
generators. But I didn't want to fight against Thor's hijacking of ruby classes.
|
218
|
+
|
219
|
+
### Brief note about the design
|
220
|
+
|
221
|
+
One of the design principles is to limit inheritance (classical and mixin), and
|
222
|
+
most importantly to _avoid introducing new state via inheritance_. An important
|
223
|
+
corollary of this is that the _application objects_, ie. your task classes,
|
224
|
+
must themselves take responsibility for their interface with the underlying
|
225
|
+
resources they mix in or compose, instead of those resources providing the
|
226
|
+
interface (via so-called macro-style class methods, for instance).
|
227
|
+
|
228
|
+
## Q & A
|
229
|
+
|
230
|
+
### But what about task dependency resolution?
|
231
|
+
|
232
|
+
That's what `require` and `||=` are for ;)
|
233
|
+
|
234
|
+
Seriously, you do have `Ing.invoke Some::Task, :some_method` and `Ing.execute ...`
|
235
|
+
for this kind of thing. Personally I think it's a code smell to put reusable
|
236
|
+
code in things that are _also_ run from the command line. Is it application or
|
237
|
+
library code? Controller or model? But `invoke` is there if you must, hopefully
|
238
|
+
with a suitably ugly syntax to dissuade you. :P
|
239
|
+
|
240
|
+
### But what about security?
|
241
|
+
|
242
|
+
Yes, this means any ruby library and even built-in classes can be exercised from
|
243
|
+
the command line... but so what?
|
244
|
+
|
245
|
+
1. You can't run module methods, and the objects you invoke need to have a
|
246
|
+
hash constructor. So Kernel, Process, IO, File, etc. are pretty much ruled out.
|
247
|
+
Most of the ruby built-in classes are ruled out in fact.
|
248
|
+
|
249
|
+
2. More to the point, you're already in a shell with much more dangerous knives
|
250
|
+
lying around. You had better trust the scripts you're working with!
|
251
|
+
|
data/TASKS.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
### More about Tasks
|
2
|
+
|
3
|
+
Incidentally, there's nothing stopping you from implementing the "Test" example
|
4
|
+
functionally. It could look (simplifying a little) like this:
|
5
|
+
|
6
|
+
Test = lambda {|arg|
|
7
|
+
type = lambda {|*dirs|
|
8
|
+
dirs.each do |dir|
|
9
|
+
Dir["./test/#{dir}/*.rb"].each { |f| require_relative f }
|
10
|
+
end
|
11
|
+
}
|
12
|
+
suite = lambda { type['unit','functional','acceptance'] }
|
13
|
+
arg ? type[arg] : suite[]
|
14
|
+
}
|
15
|
+
|
16
|
+
Ing can either invoke class instance methods (on things that respond to `new`),
|
17
|
+
or call Procs directly. The advantage of using classes is that some of the
|
18
|
+
argument parsing is done for you (especially true of option arguments as we'll
|
19
|
+
see in a minute). You can use methods to basically model the command line
|
20
|
+
syntax. The advantage of Procs is you have more flexibility in how arguments
|
21
|
+
are interpreted.
|
data/bin/ing
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Usage:
|
2
|
+
# ing rspec:convert which is equivalent to
|
3
|
+
# ing rspec:convert files './{test,spec}/**/*.rb' --convert-dir 'converted'
|
4
|
+
#
|
5
|
+
module Rspec
|
6
|
+
|
7
|
+
class Convert
|
8
|
+
|
9
|
+
GSUBS = \
|
10
|
+
[
|
11
|
+
[ /^(\s*)(.+)\.should\s+be_true/ , '\1assert \2' ],
|
12
|
+
[ /^(\s*)(.+)\.should\s+be_false/ , '\1refute \2' ],
|
13
|
+
[ /^(\s*)(.+)\.should\s*==\s*(.+)$/ , '\1assert_equal \3, \2' ],
|
14
|
+
[ /^(\s*)(.+)\.should_not\s*==\s*(.+)$/ , '\1refute_equal \3, \2' ],
|
15
|
+
[ /^(\s*)(.+)\.should\s*=~\s*(.+)$/ , '\1assert_match(\3, \2)' ],
|
16
|
+
[ /^(\s*)(.+)\.should_not\s*=~\s*(.+)$/ , '\1refute_match(\3, \2)' ],
|
17
|
+
[ /^(\s*)(.+)\.should\s+be_(.+)$/ , '\1assert \2.\3?' ],
|
18
|
+
[ /^(\s*)(.+)\.should_not\s+be_(.+)$/ , '\1refute \2.\3?' ],
|
19
|
+
[ /expect\s+\{(.+)\}\.to\s+raise_error\s*\((.*)\)\s*\Z/m,
|
20
|
+
'assert_raises(\2) {\1}' ],
|
21
|
+
[ /\{(.+)\}\.should raise_error\s*\((.*)\)\s*\Z/m,
|
22
|
+
'assert_raises(\2) {\1}' ],
|
23
|
+
# these next aren't quite right because they need to wrap the next
|
24
|
+
# lines as a lambda. Thus the FIXME notes.
|
25
|
+
[ /\.should_receive\(([^\)]+)\)\.and_return\((.+)\)/,
|
26
|
+
'.stub(\1, \2) do |s| # FIXME' ],
|
27
|
+
[ /.stub\!\(([\w:]+)\)\.and_return\((.+)\)/,
|
28
|
+
'.stub(\1, \2) do |s| # FIXME' ]
|
29
|
+
|
30
|
+
]
|
31
|
+
|
32
|
+
def self.specify_options(expect)
|
33
|
+
expect.banner "Convert rspec 'should/not' matchers to minitest 'assert/refute'"
|
34
|
+
expect.text "It's not magic, you still need to hand edit your test files after running this"
|
35
|
+
expect.text "\nUsage:"
|
36
|
+
expect.text " ing rspec:convert # which is equivalent to"
|
37
|
+
expect.text " ing rspec:convert files './{test,spec}/**/*.rb' --convert-dir 'converted'"
|
38
|
+
expect.text "\nOptions:"
|
39
|
+
expect.opt :pattern, "Directory glob pattern for test files",
|
40
|
+
:type => :string, :default => './{test,spec}/**/*.rb'
|
41
|
+
expect.opt :convert_dir, "Subdirectory to save converted files",
|
42
|
+
:type => :string, :default => 'converted'
|
43
|
+
end
|
44
|
+
|
45
|
+
include Ing::Files
|
46
|
+
|
47
|
+
attr_accessor :shell, :options, :destination_root, :source_root
|
48
|
+
|
49
|
+
def destination_root; @destination_root ||= Dir.pwd; end
|
50
|
+
def source_root; @source_root ||= File.dirname(__FILE__); end
|
51
|
+
|
52
|
+
def input_files
|
53
|
+
@input_files ||= Dir[ File.expand_path(options[:pattern], source_root) ]
|
54
|
+
end
|
55
|
+
|
56
|
+
def converted_files
|
57
|
+
input_files.map {|f|
|
58
|
+
File.join( File.dirname(f), options[:convert_dir], File.basename(f) )
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def conversion_map
|
63
|
+
input_files.zip(converted_files)
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize(options)
|
67
|
+
self.options = options
|
68
|
+
end
|
69
|
+
|
70
|
+
def call(pattern=nil)
|
71
|
+
options[:pattern] = pattern || options[:pattern]
|
72
|
+
shell.say "Processing #{input_files.length} input files: #{options[:pattern]}" if verbose?
|
73
|
+
conversion_map.each do |input_f, output_f|
|
74
|
+
new_lines = convert_lines(input_f)
|
75
|
+
create_file output_f, new_lines.join
|
76
|
+
end
|
77
|
+
end
|
78
|
+
alias :files :call
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def convert_lines(fname)
|
83
|
+
count = 0; accum = []
|
84
|
+
File.open(fname) do |f|
|
85
|
+
f.each_line do |line|
|
86
|
+
new_line = GSUBS.inject(line) do |str, (rx, replace)|
|
87
|
+
str = str.gsub(rx, replace)
|
88
|
+
end
|
89
|
+
count += 1 if line != new_line
|
90
|
+
accum << new_line
|
91
|
+
end
|
92
|
+
end
|
93
|
+
shell.say_status(:convert,
|
94
|
+
"#{relative_to_original_destination_root(fname)}: " +
|
95
|
+
"#{count} changes",
|
96
|
+
:green) if verbose?
|
97
|
+
accum
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/ing.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path("lib/ing/version", File.dirname(__FILE__))
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "ing"
|
5
|
+
s.version = Ing::VERSION
|
6
|
+
s.authors = ["Eric Gjertsen"]
|
7
|
+
s.email = ["ericgj72@gmail.com"]
|
8
|
+
s.homepage = "https://github.com/ericgj/ing"
|
9
|
+
s.summary = %q{Vanilla ruby command-line scripting}
|
10
|
+
s.description = %q{
|
11
|
+
An alternative to Rake and Thor, Ing has a command-line syntax similar to
|
12
|
+
Thor's, and it incorporates Thor's (Rails') generator methods and shell
|
13
|
+
conventions. But unlike Thor or Rake, it does not define its own DSL. Your tasks
|
14
|
+
correspond to plain ruby classes and methods. Ing just handles routing from the
|
15
|
+
command line to them, and setting options. Your classes (or even Procs) do the
|
16
|
+
rest.
|
17
|
+
}
|
18
|
+
|
19
|
+
s.rubyforge_project = ""
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
s.executables << 'ing'
|
25
|
+
|
26
|
+
s.requirements << "ruby >= 1.9"
|
27
|
+
|
28
|
+
s.add_development_dependency 'fakeweb'
|
29
|
+
end
|