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
@@ -0,0 +1,64 @@
1
+ 
2
+ module Ing
3
+ module Commands
4
+
5
+ # This is the boot command invoked from `ing generate ...`
6
+ class Generate < Boot
7
+
8
+ DEFAULTS = {
9
+ namespace: 'object',
10
+ ing_file: 'ing.rb',
11
+ gen_root: ENV.fetch('ING_GENERATOR_ROOT', '~/.ing/generators')
12
+ }
13
+
14
+ def self.specify_options(parser)
15
+ parser.text "Run a generator task"
16
+ parser.opt :debug, "Display debug messages"
17
+ parser.opt :namespace, "Top-level namespace for generators",
18
+ :type => :string, :default => DEFAULTS[:namespace]
19
+ parser.opt :gen_root, "Generators root directory",
20
+ :type => :string, :short => 'r',
21
+ :default => DEFAULTS[:gen_root]
22
+ parser.opt :ing_file, "Default generator file (ruby)",
23
+ :type => :string, :short => 'f',
24
+ :default => DEFAULTS[:ing_file]
25
+ parser.stop_on_unknown
26
+ end
27
+
28
+ def generator_root
29
+ @generator_root ||= File.expand_path(options[:gen_root])
30
+ end
31
+
32
+ # Locate and require the generator ruby file identified by the first arg,
33
+ # before dispatching to it.
34
+ def before(*args)
35
+ require_generator args.first
36
+ end
37
+
38
+ private
39
+
40
+ def require_generator(name)
41
+ path = File.expand_path(generator_name_to_path(name), generator_root)
42
+ f = if File.directory?(path)
43
+ File.join(path, options[:ing_file])
44
+ else
45
+ path
46
+ end
47
+ debug "#{__FILE__}:#{__LINE__} :: require #{f.inspect}"
48
+ require f
49
+ rescue LoadError
50
+ raise LoadError,
51
+ "No generator found named `#{name}`. Check that you have set the generator root directory correctly (looking for `#{f}`)"
52
+ end
53
+
54
+ def generator_name_to_path(name)
55
+ name.split(":").join("/")
56
+ end
57
+
58
+ end
59
+
60
+ # alias
61
+ G = Generate
62
+
63
+ end
64
+ end
@@ -0,0 +1,87 @@
1
+ module Ing
2
+ module Commands
3
+
4
+ class Help
5
+
6
+ DEFAULTS = {
7
+ namespace: 'ing:commands',
8
+ ing_file: 'ing.rb'
9
+ }
10
+
11
+ def self.specify_options(parser)
12
+ parser.text "Display help on specified command"
13
+ parser.text "\nUsage:"
14
+ parser.text " ing help generate # help on built-in command generate"
15
+ parser.text " ing help --namespace test unit # help on command within namespace"
16
+ parser.text " ing help # display this message"
17
+ parser.text "\nOptions:"
18
+ parser.opt :debug, "Display debug messages"
19
+ parser.opt :namespace, "Top-level namespace",
20
+ :type => :string, :default => DEFAULTS[:namespace]
21
+ parser.opt :require, "Require file or library before running (multi)",
22
+ :multi => true, :type => :string
23
+ parser.opt :ing_file, "Default task file (ruby)",
24
+ :type => :string, :short => 'f',
25
+ :default => DEFAULTS[:ing_file]
26
+ end
27
+
28
+ attr_accessor :options
29
+ attr_writer :shell
30
+
31
+ def shell
32
+ @shell ||= ::Ing.shell_class.new
33
+ end
34
+
35
+ def initialize(options)
36
+ self.options = options
37
+ debug "#{__FILE__}:#{__LINE__} :: options #{options.inspect}"
38
+ end
39
+
40
+ # Require each passed file or library before running
41
+ # and require the ing file if it exists
42
+ def before(*args)
43
+ require_libs options[:require]
44
+ require_ing_file
45
+ end
46
+
47
+ def call(cmd="help")
48
+ before(cmd)
49
+ ns = Ing::Util.to_class_names(options[:namespace] || 'object')
50
+ cs = Ing::Util.to_class_names(cmd)
51
+ help = Dispatcher.new(ns, cs).help
52
+ shell.say help.read
53
+ end
54
+
55
+ private
56
+
57
+ # require relative paths relative to the Dir.pwd
58
+ # otherwise, require as given (so gems can be required, etc.)
59
+ def require_libs(libs)
60
+ libs = Array(libs)
61
+ libs.each do |lib|
62
+ f = if /\A\.{1,2}\// =~ lib
63
+ File.expand_path(lib)
64
+ else
65
+ lib
66
+ end
67
+ debug "#{__FILE__}:#{__LINE__} :: require #{f.inspect}"
68
+ require f
69
+ end
70
+ end
71
+
72
+ def require_ing_file
73
+ f = File.expand_path(options[:ing_file])
74
+ require_libs(f) if File.exists?(f)
75
+ end
76
+
77
+ # Internal debugging
78
+ def debug(*args)
79
+ shell.debug(*args) if options[:debug]
80
+ end
81
+
82
+ end
83
+
84
+ H = Help
85
+
86
+ end
87
+ end
@@ -0,0 +1,59 @@
1
+ 
2
+ module Ing
3
+ module Commands
4
+
5
+ # This is the default boot command when ARGV.first not recognized as
6
+ # a built-in Ing command. For example, `ing some:task run` .
7
+ class Implicit < Boot
8
+
9
+ DEFAULTS = {
10
+ namespace: 'object',
11
+ ing_file: 'ing.rb'
12
+ }
13
+
14
+ def self.specify_options(parser)
15
+ parser.text "(internal)"
16
+ parser.opt :debug, "Display debug messages"
17
+ parser.opt :namespace, "Top-level namespace for generators",
18
+ :type => :string, :default => DEFAULTS[:namespace]
19
+ parser.opt :require, "Require file or library before running (multi)",
20
+ :multi => true, :type => :string
21
+ parser.opt :ing_file, "Default generator file (ruby)",
22
+ :type => :string, :short => 'f',
23
+ :default => DEFAULTS[:ing_file]
24
+ parser.stop_on_unknown
25
+ end
26
+
27
+ # Require each passed file or library before running
28
+ # and require the ing file if it exists
29
+ def before(*args)
30
+ require_libs options[:require]
31
+ require_ing_file
32
+ end
33
+
34
+ private
35
+
36
+ # require relative paths relative to the Dir.pwd
37
+ # otherwise, require as given (so gems can be required, etc.)
38
+ def require_libs(libs)
39
+ libs = Array(libs)
40
+ libs.each do |lib|
41
+ f = if /\A\.{1,2}\// =~ lib
42
+ File.expand_path(lib)
43
+ else
44
+ lib
45
+ end
46
+ debug "#{__FILE__}:#{__LINE__} :: require #{f.inspect}"
47
+ require f
48
+ end
49
+ end
50
+
51
+ def require_ing_file
52
+ f = File.expand_path(options[:ing_file])
53
+ require_libs(f) if File.exists?(f)
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,108 @@
1
+ module Ing
2
+ module Commands
3
+
4
+ class List
5
+
6
+ DEFAULTS = {
7
+ namespace: 'ing:commands',
8
+ ing_file: 'ing.rb'
9
+ }
10
+
11
+ def self.specify_options(parser)
12
+ parser.banner "List all tasks within specified namespace"
13
+ parser.text "\nUsage:"
14
+ parser.text " ing list # list all built-in ing commands"
15
+ parser.text " ing list rspec # list all ing commands in rspec namespace, or"
16
+ parser.text " ing list --namespace rspec"
17
+ parser.text "\nOptions:"
18
+ parser.opt :debug, "Display debug messages"
19
+ parser.opt :namespace, "Top-level namespace",
20
+ :type => :string, :default => DEFAULTS[:namespace]
21
+ parser.opt :require, "Require file or library before running (multi)",
22
+ :multi => true, :type => :string
23
+ parser.opt :ing_file, "Default task file (ruby)",
24
+ :type => :string, :short => 'f',
25
+ :default => DEFAULTS[:ing_file]
26
+ end
27
+
28
+ attr_accessor :options
29
+ attr_writer :shell
30
+
31
+ def shell
32
+ @shell ||= ::Ing.shell_class.new
33
+ end
34
+
35
+ def initialize(options)
36
+ self.options = options
37
+ debug "#{__FILE__}:#{__LINE__} :: options #{options.inspect}"
38
+ end
39
+
40
+ # Require each passed file or library before running
41
+ # and require the ing file if it exists
42
+ def before(*args)
43
+ require_libs options[:require]
44
+ require_ing_file
45
+ end
46
+
47
+
48
+ def call(namespace=options[:namespace])
49
+ before(namespace)
50
+ ns = Ing::Util.to_class_names(namespace)
51
+ mod = Ing::Util.namespaced_const_get(ns)
52
+ data = mod.constants.map do |c|
53
+ desc = Dispatcher.new(ns, [c]).describe
54
+ [ "ing #{Ing::Util.encode_class_names(ns + [c])}",
55
+ (desc.gets || '(no description)').chomp
56
+ ]
57
+ end.sort
58
+ shell.say desc_lines(ns, data).join("\n")
59
+ end
60
+
61
+ private
62
+
63
+ # require relative paths relative to the Dir.pwd
64
+ # otherwise, require as given (so gems can be required, etc.)
65
+ def require_libs(libs)
66
+ libs = Array(libs)
67
+ libs.each do |lib|
68
+ f = if /\A\.{1,2}\// =~ lib
69
+ File.expand_path(lib)
70
+ else
71
+ lib
72
+ end
73
+ debug "#{__FILE__}:#{__LINE__} :: require #{f.inspect}"
74
+ require f
75
+ end
76
+ end
77
+
78
+ def require_ing_file
79
+ f = File.expand_path(options[:ing_file])
80
+ require_libs(f) if File.exists?(f)
81
+ end
82
+
83
+ def desc_lines(ns, data)
84
+ colwidths = data.inject([0,0]) {|max, (line, desc)|
85
+ max[0] = line.length if line.length > max[0]
86
+ max[1] = desc.length if desc.length > max[1]
87
+ max
88
+ }
89
+ ["#{ns.join(' ')}: all tasks",
90
+ "-" * 80
91
+ ] +
92
+ data.map {|line, desc|
93
+ [ line.ljust(colwidths[0]),
94
+ desc[0...(80 - colwidths[0] - 3)]
95
+ ].join(" # ")
96
+ }
97
+ end
98
+
99
+ # Internal debugging
100
+ def debug(*args)
101
+ shell.debug(*args) if options[:debug]
102
+ end
103
+
104
+ end
105
+
106
+ L = List
107
+ end
108
+ end
@@ -0,0 +1,132 @@
1
+ require 'stringio'
2
+ require 'set'
3
+
4
+ module Ing
5
+
6
+ class Dispatcher
7
+
8
+ # Global set of dispatched commands as [dispatch_class, dispatch_meth],
9
+ # updated before dispatch
10
+ def self.dispatched
11
+ @dispatched ||= Set.new
12
+ end
13
+
14
+ # +Ing.invoke+
15
+ def self.invoke(klass, *args, &config)
16
+ allocate.tap {|d| d.initialize_preloaded(true, klass, *args) }.
17
+ dispatch(&config)
18
+ end
19
+
20
+ # +Ing.execute+
21
+ def self.execute(klass, *args, &config)
22
+ allocate.tap {|d| d.initialize_preloaded(false, klass, *args) }.
23
+ dispatch(&config)
24
+ end
25
+
26
+ attr_accessor :dispatch_class, :dispatch_meth, :args, :options
27
+
28
+ # True if current dispatch class/method has been dispatched before
29
+ def dispatched?
30
+ Dispatcher.dispatched.include?([dispatch_class,dispatch_meth])
31
+ end
32
+
33
+ # Default constructor from +Ing.run+ (command line)
34
+ def initialize(namespaces, classes, *args)
35
+ ns = Util.namespaced_const_get(namespaces)
36
+ self.dispatch_class = Util.namespaced_const_get(classes, ns)
37
+ self.dispatch_meth = extract_method!(args, dispatch_class)
38
+ self.options = parse_options!(args, dispatch_class) || {}
39
+ self.args = args
40
+ @invoking = false
41
+ end
42
+
43
+ # Alternate constructor for preloaded object and arguments
44
+ # i.e. from +invoke+ or +execute+ instead of +run+
45
+ def initialize_preloaded(invoking, klass, *args)
46
+ self.options = (Hash === args.last ? args.pop : {})
47
+ self.dispatch_class = klass
48
+ self.dispatch_meth = extract_method!(args, dispatch_class)
49
+ self.args = args
50
+ @invoking = invoking
51
+ end
52
+
53
+ # Returns stream (StringIO) of description text from specify_options.
54
+ # Note this does not parse the options. Used by +Ing::Commands::List+.
55
+ def describe
56
+ s=StringIO.new
57
+ with_option_parser(self.dispatch_class) do |p|
58
+ p.educate_banner s
59
+ end
60
+ s.rewind; s
61
+ end
62
+
63
+ # Returns stream (StringIO) of help text from specify_options.
64
+ # Note this does not parse the options. Used by +Ing::Commands::Help+.
65
+ def help
66
+ s=StringIO.new
67
+ with_option_parser(self.dispatch_class) do |p|
68
+ p.educate s
69
+ end
70
+ s.rewind; s
71
+ end
72
+
73
+ # Public dispatch method used by all types of dispatch (run, invoke,
74
+ # execute). Does not dispatch if invoking and already dispatched.
75
+ def dispatch(&config)
76
+ unless @invoking && dispatched?
77
+ record_dispatch
78
+ execute(&config)
79
+ end
80
+ end
81
+
82
+ def with_option_parser(klass) # :nodoc:
83
+ return unless klass.respond_to?(:specify_options)
84
+ klass.specify_options(p = Trollop::Parser.new)
85
+ yield p
86
+ end
87
+
88
+ private
89
+
90
+ def record_dispatch
91
+ Dispatcher.dispatched.add [dispatch_class, dispatch_meth]
92
+ end
93
+
94
+ def execute
95
+ if dispatch_class.respond_to?(:new)
96
+ cmd = dispatch_class.new(options)
97
+ yield cmd if block_given?
98
+ cmd.send(dispatch_meth, *args)
99
+ else
100
+ dispatch_class.call *args, options
101
+ end
102
+ end
103
+
104
+ def parse_options!(args, klass)
105
+ with_option_parser(klass) do |p|
106
+ Trollop.with_standard_exception_handling(p) { p.parse(args) }
107
+ end
108
+ end
109
+
110
+ def extract_method!(args, klass)
111
+ return :call if args.empty?
112
+ if meth = whitelist(args.first, klass)
113
+ args.shift
114
+ else
115
+ meth = :call
116
+ end
117
+ meth
118
+ end
119
+
120
+ # Note this currently does no filtering, but basically checks for respond_to
121
+ def whitelist(meth, klass)
122
+ finder = Proc.new {|m| m == meth.to_sym}
123
+ if klass.respond_to?(:new)
124
+ klass.public_instance_methods(true).find(&finder)
125
+ else
126
+ klass.public_methods.find(&finder)
127
+ end
128
+ end
129
+
130
+ end
131
+
132
+ end