cog 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/bin/cog +6 -6
  2. data/lib/cog.rb +7 -11
  3. data/lib/cog/built_in_tools/basic.rb +1 -1
  4. data/lib/cog/built_in_tools/basic/cog_tool.rb +5 -12
  5. data/lib/cog/config.rb +47 -200
  6. data/lib/cog/config/cogfile.rb +5 -7
  7. data/lib/cog/config/lang_info.rb +1 -1
  8. data/lib/cog/config/language_methods.rb +71 -0
  9. data/lib/cog/config/project_methods.rb +35 -0
  10. data/lib/cog/config/tool_methods.rb +94 -0
  11. data/lib/cog/controllers/generator_controller.rb +7 -8
  12. data/lib/cog/controllers/template_controller.rb +18 -21
  13. data/lib/cog/controllers/tool_controller.rb +19 -22
  14. data/lib/cog/embed_context.rb +131 -0
  15. data/lib/cog/embeds.rb +84 -58
  16. data/lib/cog/embeds/file_scanner.rb +28 -5
  17. data/lib/cog/errors.rb +3 -0
  18. data/lib/cog/generator.rb +65 -31
  19. data/lib/cog/generator/file_methods.rb +1 -1
  20. data/lib/cog/generator/filters.rb +1 -1
  21. data/lib/cog/generator/language_methods.rb +3 -3
  22. data/lib/cog/helpers/string.rb +3 -3
  23. data/lib/cog/languages.rb +2 -0
  24. data/lib/cog/languages/c_language.rb +3 -27
  25. data/lib/cog/languages/c_plus_plus_language.rb +3 -8
  26. data/lib/cog/languages/c_sharp_language.rb +3 -16
  27. data/lib/cog/languages/java_language.rb +3 -16
  28. data/lib/cog/languages/java_script_language.rb +3 -16
  29. data/lib/cog/languages/language.rb +12 -12
  30. data/lib/cog/languages/mixins.rb +10 -0
  31. data/lib/cog/languages/mixins/c_style_comments.rb +23 -0
  32. data/lib/cog/languages/mixins/hash_comments.rb +19 -0
  33. data/lib/cog/languages/python_language.rb +2 -33
  34. data/lib/cog/languages/qt_project_language.rb +3 -34
  35. data/lib/cog/languages/ruby_language.rb +6 -31
  36. data/lib/cog/spec_helpers/matchers/match_maker.rb +10 -6
  37. data/lib/cog/tool.rb +61 -0
  38. data/lib/cog/tool/dsl.rb +26 -0
  39. data/lib/cog/version.rb +1 -1
  40. data/templates/basic/generator.rb.erb +1 -7
  41. data/templates/cog/custom_tool/cog_tool.rb.erb +10 -8
  42. data/templates/cog/custom_tool/generator.rb.erb.erb +3 -3
  43. data/templates/cog/custom_tool/tool.rb.erb +1 -1
  44. metadata +13 -16
  45. data/lib/cog/config/tool.rb +0 -99
  46. data/lib/cog/embeds/context.rb +0 -86
  47. data/templates/basic/template.c.erb.erb +0 -1
  48. data/templates/basic/template.cpp.erb.erb +0 -6
  49. data/templates/basic/template.cs.erb.erb +0 -6
  50. data/templates/basic/template.h.erb.erb +0 -1
  51. data/templates/basic/template.hpp.erb.erb +0 -6
  52. data/templates/basic/template.java.erb.erb +0 -1
  53. data/templates/basic/template.js.erb.erb +0 -1
  54. data/templates/basic/template.py.erb.erb +0 -1
  55. data/templates/basic/template.rb.erb.erb +0 -6
  56. data/templates/basic/template.txt.erb.erb +0 -1
@@ -1,89 +1,115 @@
1
1
  require 'cog/config'
2
- require 'cog/embeds/context'
2
+ require 'cog/embed_context'
3
3
  require 'cog/embeds/file_scanner'
4
+ require 'cog/errors'
4
5
 
5
6
  module Cog
6
7
 
8
+ # @api developer
7
9
  # Methods for querying and manipulating project files
8
10
  module Embeds
9
11
 
10
12
  # Search through all project files for cog embeds, and remember them so that generators can refer to them later
11
13
  def gather_from_project
12
14
  @embeds ||= {}
13
- @embed_pattern ||= "cog\\s*:\\s*([-A-Za-z0-9_.]+)\\s*(?:\\(\\s*(.+?)\\s*\\))?(\\s*once\\s*)?(\\s*\\{)?"
14
- exts = Config.instance.language_summary.collect(&:extensions).flatten
15
- sources = Dir.glob "#{Config.instance.project_source_path}/**/*.{#{exts.join ','}}"
16
- sources.each do |filename|
17
- ext = File.extname(filename).slice(1..-1)
18
- lang = Config.instance.language_for_extension ext
19
- w = File.read filename
20
- w.scan(lang.comment_pattern(@embed_pattern)) do |m|
21
- key = m[0].split.first
22
- @embeds[key] ||= {}
23
- @embeds[key][filename] ||= 0
24
- @embeds[key][filename] += 1
15
+ Cog.supported_project_files.each do |filename|
16
+ lang = Cog.language_for filename
17
+ File.read(filename).scan(statement '[-A-Za-z0-9_.]+', :lang => lang) do |m|
18
+ hook = m[0]
19
+ @embeds[hook] ||= {}
20
+ @embeds[hook][filename] ||= 0
21
+ @embeds[hook][filename] += 1
25
22
  end
26
23
  end
27
24
  end
28
25
 
29
- # @param key [String] embed key for which to find directive occurrences
30
- # @yieldparam filename [String] name of the file in which the embed occurred
31
- # @yieldparam index [Fixnum] occurrence index of the embed, 0 for the first occurrence, 1 for the second, and so on
32
- def find(key)
33
- x = @embeds[key]
26
+ # @param hook [String] embed hook for which to find directive occurrences
27
+ # @yieldparam context [EmbedContext] describes the context in which the embed statement was found
28
+ def find(hook)
29
+ x = @embeds[hook]
34
30
  unless x.nil?
35
31
  x.keys.sort.each do |filename|
36
- x[filename].times do |index|
37
- yield filename, index
32
+ c = EmbedContext.new hook, filename, x[filename]
33
+ Cog.activate_language :ext => c.extension do
34
+ c.count.times do |index|
35
+ c.index = index
36
+ yield c
37
+ end
38
38
  end
39
39
  end
40
40
  end
41
41
  end
42
42
 
43
- # @param key [String] unique identifier for the embed
44
- # @param filename [String] file in which to look for the embed
45
- # @param index [Fixnum] occurrence of the embed. 0 for first, 1 for second, ...
46
- # @yieldparam context [Context] describes the context in which the directive occurred
43
+ # @param c [EmbedContext] describes the context in which the embed statement was found
44
+ # @yieldparam context [EmbedContext] describes the context in which the embed statement was found
47
45
  # @yieldreturn [String] the value to substitute into the embed expansion
48
- # @return [Boolean] whether or not the expansion was updated
49
- def update(key, filename, index, &block)
50
- c = Context.new key, filename
51
- snip_pattern = c.language.comment_pattern("cog\\s*:\\s*(#{key})\\s*(?:\\(\\s*(.+?)\\s*\\))?(\\s*once\\s*)?(?:\\s*([{]))?")
52
- end_pattern = c.language.comment_pattern("cog\\s*:\\s*[}]")
53
- not_end_pattern = c.language.comment_pattern("cog\\s*:\\s*(?!\\s*[}]).*$")
54
-
55
- s = FileScanner.new filename
56
- updated = if match = s.read_until(snip_pattern, index)
57
- if match.nil? # embed not found
58
- false
46
+ # @return [Hash] whether or not the expansion was updated
47
+ def update(c, &block)
48
+ FileScanner.scan(c.path, statement(c.hook), :occurrence => c.actual_index) do |s|
49
+ c.lineno = s.marked_line_number
50
+ c.args = s.match[2].split if s.match[2]
51
+ c.once = !s.match[3].nil?
52
+ if s.match[4] == '{'
53
+ update_body c, s, &block
59
54
  else
60
- c.lineno = s.marked_line_number
61
- c.args = match[2].split if match[2]
62
- c.once = !match[3].nil?
63
- if match[4] == '{' # embed already expanded
64
- unless s.capture_until end_pattern, :but_not => not_end_pattern
65
- raise Errors::SnippetExpansionUnterminated.new "#{filename.relative_to_project_root}:#{s.marked_line_number}"
66
- end
67
- c.body = s.captured_text
68
- value = block.call(c).rstrip
69
- if c.once? || value != s.captured_text
70
- s.replace_captured_text(value + "\n", :once => c.once?)
71
- end
72
- else # embed not yet expanded
73
- value = block.call(c).rstrip
74
- snip_line = c.language.comment "#{c.to_directive} {"
75
- unless c.once?
76
- value = [snip_line, value, c.language.comment("cog: }")].join("\n")
77
- end
78
- s.insert_at_mark(value + "\n")
79
- end
55
+ expand_body c, s, &block
80
56
  end
81
57
  end
82
- s.close
83
- updated
84
58
  end
85
59
 
86
- extend self # Singleton
60
+ private
61
+
62
+ # Pattern groups are
63
+ # * 1 - hook
64
+ # * 2 - args
65
+ # * 3 - once
66
+ # * 4 - expansion begin (curly <tt>{</tt>)
67
+ # @return [Regexp] pattern to match the beginning of a cog embed statement
68
+ def statement(hook, opt={})
69
+ lang = opt[:lang] || Cog.active_language
70
+ lang.comment_pattern("cog\\s*:\\s*(#{hook})\\s*(?:\\(\\s*(.+?)\\s*\\))?(\\s*once\\s*)?(?:\\s*([{]))?")
71
+ end
72
+
73
+ # @return [Regexp] pattern to match the end of a cog embed statement
74
+ def end_statement
75
+ Cog.active_language.comment_pattern("cog\\s*:\\s*[}]")
76
+ end
77
+
78
+ # @return [Regexp] pattern to match an line that looks like a cog statement, but is not the end statement
79
+ def anything_but_end
80
+ Cog.active_language.comment_pattern("cog\\s*:\\s*(?!\\s*[}]).*$")
81
+ end
82
+
83
+ # @param c [EmbedContext]
84
+ # @param s [FileScanner]
85
+ # @return [Boolean] whether or not the scanner updated its file
86
+ def update_body(c, s, &block)
87
+ unless s.capture_until end_statement, :but_not => anything_but_end
88
+ raise Errors::SnippetExpansionUnterminated.new "#{c.path.relative_to_project_root}:#{s.marked_line_number}"
89
+ end
90
+ c.body = s.captured_text
91
+ value = block.call(c).rstrip
92
+ if c.once? || value != s.captured_text
93
+ s.replace_captured_text(value + "\n", :once => c.once?)
94
+ end
95
+ end
96
+
97
+ # @param c [EmbedContext]
98
+ # @param s [FileScanner]
99
+ # @return [Boolean] whether or not the scanner updated its file
100
+ def expand_body(c, s, &block)
101
+ lang = Cog.active_language
102
+ value = block.call(c).rstrip
103
+ snip_line = lang.comment "#{c.to_directive} {"
104
+ unless c.once?
105
+ value = [snip_line, value, lang.comment("cog: }")].join("\n")
106
+ end
107
+ s.insert_at_mark(value + "\n")
108
+ end
109
+
110
+ public
111
+
112
+ extend self
87
113
 
88
114
  end
89
115
  end
@@ -1,9 +1,14 @@
1
1
  module Cog
2
2
  module Embeds
3
3
 
4
+ # @api developer
4
5
  # Helper for scanning files for embed expansions
5
6
  class FileScanner
6
7
 
8
+ # @return [MatchData, nil] match data for the last matched pattern
9
+ attr_reader :match
10
+
11
+ # @api developer
7
12
  def initialize(filename)
8
13
  @filename = filename
9
14
  @f = File.open filename, 'r'
@@ -11,13 +16,31 @@ module Cog
11
16
  @mark = nil
12
17
  @cap_begin_pos = nil
13
18
  @cap_end_pos = nil
19
+ @match = nil # The last match object
14
20
  end
15
-
21
+
22
+ # @api developer
16
23
  # Closes the scanned file, if it is not already closed
17
24
  def close
18
25
  @f.close unless @f.closed?
19
26
  end
20
27
 
28
+ # Opens the given file for scanning.
29
+ # @param filename [String] path to the file which will be scanned
30
+ # @param pattern [Regexp] a pattern to test for
31
+ # @option opt [Fixnum] :occurrence (0) 0 for the first, 1 for the second, and so on
32
+ # @yieldparam scanner [FileScanner] a file scanner
33
+ # @yieldreturn [Object]
34
+ # @return [Object] the return value of the block
35
+ def self.scan(filename, pattern, opt={}, &block)
36
+ s = new filename
37
+ if s.read_until pattern, opt[:occurrence] || 0
38
+ val = block.call s
39
+ end
40
+ s.close
41
+ val
42
+ end
43
+
21
44
  # Remember this position. A later call to insert_at_mark will insert at this marked position
22
45
  # @return [nil]
23
46
  def mark!
@@ -36,20 +59,20 @@ module Cog
36
59
  # Advances the file until the (n+1)th occurence of the given pattern is encountered, or the end of the file is reached
37
60
  # @param pattern [Regexp] a pattern to test for
38
61
  # @param n [Fixnum] 0 for the first, 1 for the second, and so on
39
- # @return [MatchData, nil] the match object if the pattern was found
62
+ # @return [Boolean] whether or not a match was found. Use {#match} to retrieve the match data
40
63
  def read_until(pattern, n=0)
41
64
  i = 0
42
65
  mark!
43
66
  while (line = @f.readline) && i <= n
44
- if m = pattern.match(line)
45
- return m if i == n
67
+ if @match = pattern.match(line)
68
+ return true if i == n
46
69
  i += 1
47
70
  end
48
71
  mark!
49
72
  end
50
73
  rescue EOFError
51
74
  unmark!
52
- nil
75
+ false
53
76
  end
54
77
 
55
78
  # Advances the file by one line
@@ -68,5 +68,8 @@ module Cog
68
68
  "a embed expansion in the given file is missing the 'cog: }' terminator"
69
69
  end
70
70
 
71
+ define_error :ToolMissingDefinition, 'missing' do
72
+ "the tool was not fully defined"
73
+ end
71
74
  end
72
75
  end
@@ -1,4 +1,5 @@
1
1
  require 'cog/config'
2
+ require 'cog/embeds'
2
3
  require 'cog/errors'
3
4
  require 'cog/generator/file_methods'
4
5
  require 'cog/generator/filters'
@@ -33,57 +34,90 @@ module Cog
33
34
 
34
35
  # Stamp a template into a file or return it as a string
35
36
  # @param template_path [String] path to template file relative one of the {Config#template_paths}
36
- # @param destination [String] path to which the generated file should be written, relative to the {Config#project_source_path}
37
+ # @param destination [String] path to which the generated file should be written, relative to the {Config::ProjectMethods#project_source_path}
37
38
  # @option opt [Boolean] :absolute_template_path (false) is the +template_path+ absolute?
38
39
  # @option opt [Boolean] :absolute_destination (false) is the +destination+ absolute?
39
- # @option opt [String, Array<String>] :filter (nil) filter the result through the named methods
40
+ # @option opt [Binding] :binding (nil) an optional binding to use while evaluating the template
41
+ # @option opt [String, Array<String>] :filter (nil) name(s) of {Filters}
40
42
  # @option opt [Boolean] :quiet (false) suppress writing to STDOUT?
41
43
  # @return [nil or String] if +destination+ is not provided, the stamped template is returned as a string
42
44
  def stamp(template_path, destination=nil, opt={})
43
45
  # Ignore destination if its a hash, its meant to be opt
44
46
  opt, destination = destination, nil if destination.is_a? Hash
45
47
 
46
- # Find and render the template
47
- t = get_template template_path, :absolute => opt[:absolute_template_path]
48
- b = opt[:binding] || binding
49
- r = Config.instance.activate_language :ext => File.extname(template_path.to_s) do
50
- t.result(b)
51
- end
52
-
53
- # Run r through filters
54
- f = opt[:filter]
55
- f = [f].compact unless f.is_a?(Array)
56
- f.each {|name| r = call_filter name, r }
57
-
48
+ # Render and filter
49
+ r = find_and_render template_path, opt
50
+ r = filter_through r, opt[:filter]
58
51
  return r if destination.nil?
59
52
 
60
53
  # Place it in a file
61
- dest = opt[:absolute_destination] ? destination : File.join(Config.instance.project_source_path, destination)
62
- FileUtils.mkpath File.dirname(dest) unless File.exists? dest
63
- scratch = "#{dest}.scratch"
64
- File.open(scratch, 'w') {|file| file.write r}
65
- if files_are_same? dest, scratch
66
- FileUtils.rm scratch
67
- else
68
- updated = File.exists? dest
69
- FileUtils.mv scratch, dest
70
- STDOUT.write "#{updated ? :Updated : :Created} #{dest.relative_to_project_root}\n".color(updated ? :white : :green) unless opt[:quiet]
54
+ write_scratch_file(destination, r, opt[:absolute_destination]) do |path, scratch|
55
+ if files_are_same? path, scratch
56
+ FileUtils.rm scratch
57
+ else
58
+ updated = File.exists? path
59
+ FileUtils.mv scratch, path
60
+ STDOUT.write "#{updated ? :Updated : :Created} #{path.relative_to_project_root}\n".color(updated ? :white : :green) unless opt[:quiet]
61
+ end
71
62
  end
72
63
  nil
73
64
  end
74
65
 
75
- # Provide a value for the embed with the given key
76
- # @param key [String] a unique identifier for the embed
77
- # @yieldparam context [Embeds::Context] provides information about the environment in which the embed statement was found
66
+ # Provide a value for embeds with the given hook
67
+ # @param hook [String] hook name used in the embed statements
68
+ # @yieldparam context [Embeds::EmbedContext] provides information about the environment in which the embed statement was found
78
69
  # @yieldreturn The value which will be used to expand the embed (or replace the embedded content)
79
70
  # @return [nil]
80
- def embed(key, &block)
81
- Embeds.find(key) do |filename, index|
82
- if Embeds.update key, filename, index, &block
83
- STDOUT.write "Updated #{filename.relative_to_project_root} - #{(index + 1).ordinalize} occurrence of embed '#{key}'\n".color :white
71
+ def embed(hook, &block)
72
+ eaten = 0 # keep track of eaten statements so that the index can be adjusted
73
+ Embeds.find(hook) do |c|
74
+ c.eaten = eaten
75
+ if Embeds.update c, &block
76
+ eaten += 1 if c.once?
77
+ STDOUT.write "Updated #{c.path.relative_to_project_root} - #{(c.index + 1).ordinalize} occurrence of embed '#{c.hook}'\n".color :white
84
78
  end
85
79
  end
86
80
  end
81
+
82
+ private
83
+
84
+ # @param template_path [String] path to template file relative one of the {Config#template_paths}
85
+ # @option opt [Boolean] :absolute_template_path (false) is the +template_path+ absolute?
86
+ # @option opt [Binding] :binding (nil) an optional binding to use while evaluating the template
87
+ # @return [String] result of rendering the template
88
+ def find_and_render(template_path, opt={})
89
+ t = get_template template_path, :absolute => opt[:absolute_template_path]
90
+ b = opt[:binding] || binding
91
+ Cog.activate_language :ext => File.extname(template_path.to_s) do
92
+ t.result(b)
93
+ end
94
+ end
95
+
96
+ # @param text [String] text to run through filters
97
+ # @param f [String, Array<String>] name(s) of {Filters}
98
+ def filter_through(text, f)
99
+ f = [f].compact unless f.is_a?(Array)
100
+ f.each {|name| text = call_filter name, text }
101
+ text
102
+ end
103
+
104
+ # @param original [String] path to the original file
105
+ # @param text [String] text to write into the scratch file
106
+ # @param absolute [Boolean] is the path absolute or relative to the project root?
107
+ # @yieldparam original [String] absolute path to original file
108
+ # @yieldparam scratch [String] path to the scratch file
109
+ # @return [nil]
110
+ def write_scratch_file(original, text, absolute=false, &block)
111
+ path = absolute ? original : File.join(Cog.project_source_path, original)
112
+ FileUtils.mkpath File.dirname(path) unless File.exists? path
113
+ scratch = "#{path}.scratch"
114
+ File.open(scratch, 'w') {|file| file.write text}
115
+ block.call path, scratch
116
+ end
87
117
 
118
+ public
119
+
120
+ extend self
121
+
88
122
  end
89
123
  end
@@ -15,7 +15,7 @@ module Cog
15
15
  fullpath = if opt[:absolute]
16
16
  path
17
17
  else
18
- Config.instance.template_paths.inject('') do |found, prefix|
18
+ Cog.template_paths.inject('') do |found, prefix|
19
19
  x = File.join prefix, path
20
20
  found.empty? && File.exists?(x) ? x : found
21
21
  end
@@ -9,7 +9,7 @@ module Cog
9
9
  # @param text [String] some text which should be rendered as a comment
10
10
  # @return [String] a comment appropriate for the current language context
11
11
  def comment(text)
12
- Config.instance.active_language.comment text
12
+ Cog.active_language.comment text
13
13
  end
14
14
 
15
15
  # @api developer
@@ -17,7 +17,7 @@ module Cog
17
17
  # @return [String] the scope begin statement
18
18
  def scope_begin(scope)
19
19
  gcontext[:scopes] << scope
20
- Config.instance.active_language.method("#{scope.type}_begin").call(scope.name)
20
+ Cog.active_language.method("#{scope.type}_begin").call(scope.name)
21
21
  end
22
22
 
23
23
  # End the scope, popping it off the scope stack
@@ -29,7 +29,7 @@ module Cog
29
29
  return nil
30
30
  end
31
31
  scope = gcontext[:scopes].pop
32
- Config.instance.active_language.method("#{scope.type}_end").call(scope.name)
32
+ Cog.active_language.method("#{scope.type}_end").call(scope.name)
33
33
  end
34
34
 
35
35
  # End all scope currently on the stack
@@ -45,7 +45,7 @@ module Cog
45
45
  # @param name [String] name of the scope to use
46
46
  # @return [String] a using statement for the named scope
47
47
  def use_named_scope(name)
48
- Config.instance.active_language.use_named_scope(name)
48
+ Cog.active_language.use_named_scope(name)
49
49
  end
50
50
 
51
51
  # @param name [String] name of the scope
@@ -2,10 +2,10 @@ require 'cog/config'
2
2
 
3
3
  class String
4
4
 
5
- # @return [String] strips {Cog::Config#project_root} from the beginning of this string
5
+ # @return [String] strips {Cog::Config::ProjectMethods#project_root} from the beginning of this string
6
6
  def relative_to_project_root
7
- return dup unless Cog::Config.instance.project?
8
- relative_to Cog::Config.instance.project_root
7
+ return dup unless Cog.project?
8
+ relative_to Cog.project_root
9
9
  end
10
10
 
11
11
  # @param prefix [String] path prefix to strip from the beginning of this string
@@ -1,5 +1,6 @@
1
1
  require 'cog/errors'
2
2
  require 'cog/languages/language'
3
+ require 'cog/languages/mixins'
3
4
 
4
5
  module Cog
5
6
 
@@ -15,6 +16,7 @@ module Cog
15
16
  :hpp => 'c++',
16
17
  :java => 'java',
17
18
  :js => 'javascript',
19
+ :pri => 'qt',
18
20
  :pro => 'qt',
19
21
  :py => 'python',
20
22
  :rb => 'ruby',