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