ing 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +5 -0
  2. data/GENERATORS.md +2 -0
  3. data/LICENSE +18 -0
  4. data/OPTIONS.md +2 -0
  5. data/README.md +251 -0
  6. data/TASKS.md +21 -0
  7. data/bin/ing +5 -0
  8. data/examples/rspec_convert.rb +102 -0
  9. data/ing.gemspec +29 -0
  10. data/ing.rb +102 -0
  11. data/lib/ing.rb +78 -0
  12. data/lib/ing/actions/create_file.rb +105 -0
  13. data/lib/ing/actions/create_link.rb +57 -0
  14. data/lib/ing/actions/directory.rb +98 -0
  15. data/lib/ing/actions/empty_directory.rb +155 -0
  16. data/lib/ing/actions/file_manipulation.rb +308 -0
  17. data/lib/ing/actions/inject_into_file.rb +109 -0
  18. data/lib/ing/commands/boot.rb +76 -0
  19. data/lib/ing/commands/generate.rb +64 -0
  20. data/lib/ing/commands/help.rb +87 -0
  21. data/lib/ing/commands/implicit.rb +59 -0
  22. data/lib/ing/commands/list.rb +108 -0
  23. data/lib/ing/dispatcher.rb +132 -0
  24. data/lib/ing/files.rb +190 -0
  25. data/lib/ing/lib_trollop.rb +782 -0
  26. data/lib/ing/shell.rb +390 -0
  27. data/lib/ing/trollop/parser.rb +17 -0
  28. data/lib/ing/util.rb +61 -0
  29. data/lib/ing/version.rb +3 -0
  30. data/lib/thor/actions/file_manipulation.rb +30 -0
  31. data/lib/thor/shell/basic.rb +44 -0
  32. data/test/acceptance/ing_run_tests.rb +164 -0
  33. data/test/actions/create_file_spec.rb +209 -0
  34. data/test/actions/create_link_spec.rb +90 -0
  35. data/test/actions/directory_spec.rb +167 -0
  36. data/test/actions/empty_directory_spec.rb +146 -0
  37. data/test/actions/file_manipulation_spec.rb +433 -0
  38. data/test/actions/inject_into_file_spec.rb +147 -0
  39. data/test/fixtures/application.rb +2 -0
  40. data/test/fixtures/app{1}/README +3 -0
  41. data/test/fixtures/bundle/execute.rb +6 -0
  42. data/test/fixtures/bundle/main.thor +1 -0
  43. data/test/fixtures/doc/%file_name%.rb.tt +1 -0
  44. data/test/fixtures/doc/COMMENTER +10 -0
  45. data/test/fixtures/doc/README +3 -0
  46. data/test/fixtures/doc/block_helper.rb +3 -0
  47. data/test/fixtures/doc/components/.empty_directory +0 -0
  48. data/test/fixtures/doc/config.rb +1 -0
  49. data/test/fixtures/doc/config.yaml.tt +1 -0
  50. data/test/fixtures/group.ing.rb +76 -0
  51. data/test/fixtures/invok.ing.rb +50 -0
  52. data/test/fixtures/namespace.ing.rb +52 -0
  53. data/test/fixtures/require.ing.rb +7 -0
  54. data/test/fixtures/task.ing.rb +36 -0
  55. data/test/fixtures/task.thor +10 -0
  56. data/test/spec_helper.rb +2 -0
  57. data/test/test_helper.rb +41 -0
  58. data/todo.yml +7 -0
  59. metadata +147 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ scratch
2
+ test/actions/orig
3
+ test/actions/converted
4
+ test/sandbox
5
+ *.gem
data/GENERATORS.md ADDED
@@ -0,0 +1,2 @@
1
+ TODO
2
+
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
@@ -0,0 +1,2 @@
1
+ TODO
2
+
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,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path('../lib/ing', File.dirname(__FILE__))
4
+
5
+ Ing.run
@@ -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