cog 0.2.1 → 0.2.2

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 (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',