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