cog 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,9 +10,10 @@ module Cog
10
10
  end
11
11
 
12
12
  # Activate a given language within the scope of the provided block.
13
- # Either provide <tt>key</tt> or <tt>:ext</tt> but not both. If the extension does not match any of the supported languages, the {#active_language} will not change, but the block will still be called.
13
+ # Either provide <tt>key</tt>, <tt>:ext</tt>, or <tt>:filename</tt> but not more than one. If the extension does not match any of the supported languages, the {#active_language} will not change, but the block will still be called.
14
14
  # @param key [:String] the lanuage identifier. Type <tt>cog language list</tt> to see the possible values
15
15
  # @option opt [:String] :ext (nil) a file extension which will map to a language identifier. Type <tt>cog language map</tt> to see mapped extensions
16
+ # @option opt [:String] :filename (nil) a filename or path to file which will map to a language identifier
16
17
  # @yield within this block the {#active_language} will be set to the desired value
17
18
  # @return [Object] the value returned by the block
18
19
  def activate_language(key, opt={}, &block)
@@ -22,6 +23,9 @@ module Cog
22
23
  ext = opt[:ext].to_s.downcase
23
24
  ext = ext.slice(1..-1) if ext.start_with?('.')
24
25
  @language_extension_map[ext] unless ext.empty?
26
+ elsif opt[:filename]
27
+ ext = File.extname(opt[:filename]).slice(1..-1)
28
+ @language_extension_map[ext] unless ext.nil? || ext.empty?
25
29
  else
26
30
  key
27
31
  end
@@ -38,6 +38,7 @@ module Cog
38
38
  @path = path.to_s
39
39
  @count = count
40
40
  @eaten = 0
41
+ @index = 0
41
42
  end
42
43
 
43
44
  # @return [Array<String>] arguments provided with the embed statement
@@ -74,6 +75,9 @@ module Cog
74
75
  @once
75
76
  end
76
77
 
78
+ # @api developer
79
+ attr_accessor :keep_body
80
+
77
81
  # @api developer
78
82
  # @param value [String, nil] set the body to this value
79
83
  def body=(value)
@@ -115,11 +119,11 @@ module Cog
115
119
  def actual_index
116
120
  @index - @eaten
117
121
  end
118
-
122
+
119
123
  # @api developer
120
124
  # @return [String]
121
- def to_directive
122
- x = "cog: #{hook}"
125
+ def to_statement(type='cog')
126
+ x = "#{type}: #{hook}"
123
127
  x += "(#{args.join ' '})" if args
124
128
  x += " once" if once?
125
129
  x
@@ -8,14 +8,54 @@ module Cog
8
8
  def gather_from_project
9
9
  @embeds ||= {}
10
10
  Cog.supported_project_files.each do |filename|
11
- lang = Cog.language_for filename
12
- File.read(filename).scan(statement '[-A-Za-z0-9_.]+', :lang => lang) do |m|
13
- hook = m[0]
14
- @embeds[hook] ||= {}
15
- @embeds[hook][filename] ||= 0
16
- @embeds[hook][filename] += 1
11
+ gather_from_file filename, :hash => @embeds, :type => 'cog'
12
+ end
13
+ end
14
+
15
+ # Copy keep bodies from the original file to the scratch file
16
+ # @param original [String] file in which to search for keep statements. If the original does not exist, then scratch will serve as the original (we do this so that the keeps will get expanded in any case)
17
+ # @param scratch [String] file to which keep bodies will be copied
18
+ # @return [nil]
19
+ def copy_keeps(original, scratch)
20
+ keeps = {}
21
+ Cog.activate_language(:filename => original) do
22
+ original = scratch unless File.exists? original
23
+ gather_from_file(original, :type => 'keep').each_pair do |hook, count|
24
+ c = keeps[hook] = EmbedContext.new(hook, scratch, count[original])
25
+ raise Errors::DuplicateKeep.new :hook => hook if c.count > 1
26
+ Helpers::FileScanner.scan(original, statement('keep', hook)) do |s|
27
+ c.keep_body = if s.match[4] == '{'
28
+ s.capture_until end_statement('keep')
29
+ s.captured_text
30
+ else
31
+ ''
32
+ end
33
+ end
17
34
  end
35
+ keeps.each_pair do |hook, c|
36
+ result = update c, :type => 'keep' do |c|
37
+ c.keep_body
38
+ end
39
+ raise Errors::UnrecognizedKeepHook.new :hook => hook, :filename => original if result.nil?
40
+ end
41
+ end
42
+ end
43
+
44
+ # @param filename [String] file from which to gather statements
45
+ # @option opt [Hash] :hash ({}) object in which to gather the mapping
46
+ # @option opt [String] :type ('cog') one of <tt>'cog'</tt> or <tt>'keep'</tt>
47
+ # @return [Hash] mapping from hooks to <tt>{ 'filename' => count }</tt> hashes
48
+ def gather_from_file(filename, opt={})
49
+ bucket = opt[:hash] || {}
50
+ type = opt[:type] || 'cog'
51
+ lang = Cog.language_for filename
52
+ File.read(filename).scan(statement type, '[-A-Za-z0-9_.]+', :lang => lang) do |m|
53
+ hook = m[0]
54
+ bucket[hook] ||= {}
55
+ bucket[hook][filename] ||= 0
56
+ bucket[hook][filename] += 1
18
57
  end
58
+ bucket
19
59
  end
20
60
 
21
61
  # @param hook [String] embed hook for which to find directive occurrences
@@ -36,18 +76,20 @@ module Cog
36
76
  end
37
77
 
38
78
  # @param c [EmbedContext] describes the context in which the embed statement was found
79
+ # @option opt [String] :type ('cog') one of <tt>'cog'</tt> or <tt>'keep'</tt>
39
80
  # @yieldparam context [EmbedContext] describes the context in which the embed statement was found
40
81
  # @yieldreturn [String] the value to substitute into the embed expansion
41
- # @return [Hash] whether or not the expansion was updated
42
- def update(c, &block)
43
- Helpers::FileScanner.scan(c.path, statement(c.hook), :occurrence => c.actual_index) do |s|
82
+ # @return [Boolean,nil] +true+ if the statement was expanded or updated, +false+ if the statement was found, but not changed, +nil+ if it could not be found.
83
+ def update(c, opt={}, &block)
84
+ type = opt[:type] || 'cog'
85
+ Helpers::FileScanner.scan(c.path, statement(type, c.hook), :occurrence => c.actual_index) do |s|
44
86
  c.lineno = s.marked_line_number
45
87
  c.args = s.match[2].split if s.match[2]
46
88
  c.once = !s.match[3].nil?
47
89
  if s.match[4] == '{'
48
- update_body c, s, &block
90
+ update_body c, s, opt, &block
49
91
  else
50
- expand_body c, s, &block
92
+ expand_body c, s, opt, &block
51
93
  end
52
94
  end
53
95
  end
@@ -60,44 +102,50 @@ module Cog
60
102
  # * 3 - once
61
103
  # * 4 - expansion begin (curly <tt>{</tt>)
62
104
  # @return [Regexp] pattern to match the beginning of a cog embed statement
63
- def statement(hook, opt={})
105
+ def statement(type, hook, opt={})
64
106
  lang = opt[:lang] || Cog.active_language
65
- lang.comment_pattern("cog\\s*:\\s*(#{hook})\\s*(?:\\(\\s*(.+?)\\s*\\))?(\\s*once\\s*)?(?:\\s*([{]))?")
107
+ lang.comment_pattern("#{type}\\s*:\\s*(#{hook})\\s*(?:\\(\\s*(.+?)\\s*\\))?(\\s*once\\s*)?(?:\\s*([{]))?")
66
108
  end
67
109
 
68
110
  # @return [Regexp] pattern to match the end of a cog embed statement
69
- def end_statement
70
- Cog.active_language.comment_pattern("cog\\s*:\\s*[}]")
111
+ def end_statement(type = 'cog')
112
+ Cog.active_language.comment_pattern("#{type}\\s*:\\s*[}]")
71
113
  end
72
114
 
73
115
  # @return [Regexp] pattern to match an line that looks like a cog statement, but is not the end statement
74
- def anything_but_end
75
- Cog.active_language.comment_pattern("cog\\s*:\\s*(?!\\s*[}]).*$")
116
+ def anything_but_end(type = 'cog')
117
+ Cog.active_language.comment_pattern("#{type}\\s*:\\s*(?!\\s*[}]).*$")
76
118
  end
77
119
 
78
120
  # @param c [EmbedContext]
79
121
  # @param s [FileScanner]
122
+ # @option opt [String] :type ('cog') one of <tt>'cog'</tt> or <tt>'keep'</tt>
80
123
  # @return [Boolean] whether or not the scanner updated its file
81
- def update_body(c, s, &block)
82
- unless s.capture_until end_statement, :but_not => anything_but_end
83
- raise Errors::SnippetExpansionUnterminated.new "#{c.path.relative_to_project_root}:#{s.marked_line_number}"
124
+ def update_body(c, s, opt={}, &block)
125
+ type = opt[:type] || 'cog'
126
+ unless s.capture_until end_statement(type), :but_not => anything_but_end(type)
127
+ raise Errors::SnippetExpansionUnterminated.new :filename => c.path.relative_to_project_root, :line => s.marked_line_number
84
128
  end
85
129
  c.body = s.captured_text
86
130
  value = block.call(c).rstrip
87
131
  if c.once? || value != s.captured_text
88
132
  s.replace_captured_text(value + "\n", :once => c.once?)
133
+ else
134
+ false
89
135
  end
90
136
  end
91
137
 
92
138
  # @param c [EmbedContext]
93
139
  # @param s [FileScanner]
140
+ # @option opt [String] :type ('cog') one of <tt>'cog'</tt> or <tt>'keep'</tt>
94
141
  # @return [Boolean] whether or not the scanner updated its file
95
- def expand_body(c, s, &block)
142
+ def expand_body(c, s, opt={}, &block)
143
+ type = opt[:type] || 'cog'
96
144
  lang = Cog.active_language
97
145
  value = block.call(c).rstrip
98
- snip_line = lang.comment "#{c.to_directive} {"
146
+ snip_line = lang.comment "#{c.to_statement type} {"
99
147
  unless c.once?
100
- value = [snip_line, value, lang.comment("cog: }")].join("\n")
148
+ value = [snip_line, value, lang.comment("#{type}: }")].join("\n")
101
149
  end
102
150
  s.insert_at_mark(value + "\n")
103
151
  end
@@ -4,58 +4,69 @@ module Cog
4
4
 
5
5
  # Root type for all cog errors
6
6
  class CogError < Exception
7
+ def initialize(details={})
8
+ @details = if details.is_a? Hash
9
+ details.to_a.collect do |key, value|
10
+ "#{key} => #{value.inspect}"
11
+ end.sort
12
+ else
13
+ [details]
14
+ end
15
+ end
16
+
17
+ def message
18
+ w = custom_message || self.class.name.underscore.split('/').last.gsub('_', ' ')
19
+ w += " (#{@details.join ', '})" unless @details.empty?
20
+ w
21
+ end
7
22
  end
8
23
 
9
24
  # Define a +cog+ error class
10
25
  # @api developer
11
- #
12
26
  # @param class_name [String] name of the error class
13
- # @param arg [String] name of the argument to the constructor, will appear in error messages
14
- # @yield +self+ will be set to an instance of the error class and <tt>@msg</tt> will contain
15
- def self.define_error(class_name, arg, &block)
27
+ def self.define_error(class_name, &block)
16
28
  cls = Class.new CogError
17
29
  Errors.instance_eval { const_set class_name, cls }
18
30
  cls.instance_eval do
19
- define_method(:initialize) {|value| @value = value}
20
- define_method :message do
21
- msg = if block.nil?
22
- class_name.to_s.underscore.gsub '_', ' '
23
- else
24
- instance_eval &block
25
- end
26
- "#{msg} (#{arg} => #{@value})"
31
+ define_method :custom_message do
32
+ block.call if block
27
33
  end
28
34
  end
29
35
  end
30
36
 
31
- define_error :ActionRequiresProjectGeneratorPath, 'action'
32
- define_error :ActionRequiresProjectTemplatePath, 'action'
33
- define_error :ActionRequiresProjectPluginPath, 'action'
37
+ define_error :ActionRequiresProjectGeneratorPath
38
+ define_error :ActionRequiresProjectTemplatePath
39
+ define_error :ActionRequiresProjectPluginPath
34
40
 
35
- define_error :DuplicateGenerator, 'generator'
36
- define_error :DuplicatePlugin, 'plugin'
41
+ define_error :DuplicateGenerator
42
+ define_error :DuplicatePlugin
43
+ define_error :DuplicateKeep
37
44
 
38
- define_error :InvalidPluginConfiguration, 'path to cog_plugin.rb file' do
45
+ define_error :InvalidPluginConfiguration do
39
46
  "invalid directory structure for a cog plugin"
40
47
  end
41
48
 
42
- define_error :NoSuchFilter, 'filter'
43
- define_error :NoSuchGenerator, 'generator'
44
- define_error :NoSuchLanguage, 'language'
45
- define_error :NoSuchTemplate, 'template'
46
- define_error :NoSuchPlugin, 'plugin'
49
+ define_error :UnrecognizedKeepHook do
50
+ "looks like that hook is longer being generated"
51
+ end
52
+
53
+ define_error :NoSuchFilter
54
+ define_error :NoSuchGenerator
55
+ define_error :NoSuchLanguage
56
+ define_error :NoSuchTemplate
57
+ define_error :NoSuchPlugin
47
58
 
48
- define_error :PluginPathIsNotADirectory, 'plugin_path'
59
+ define_error :PluginPathIsNotADirectory
49
60
 
50
- define_error :ScopeStackUnderflow, 'caller' do
61
+ define_error :ScopeStackUnderflow do
51
62
  "scope stack underflow: this can happen if you have too many *_end calls in a template"
52
63
  end
53
64
 
54
- define_error :SnippetExpansionUnterminated, 'location' do
65
+ define_error :SnippetExpansionUnterminated do
55
66
  "a embed expansion in the given file is missing the 'cog: }' terminator"
56
67
  end
57
68
 
58
- define_error :PluginMissingDefinition, 'missing' do
69
+ define_error :PluginMissingDefinition do
59
70
  "the plugin was not fully defined"
60
71
  end
61
72
  end
@@ -47,10 +47,11 @@ module Cog
47
47
 
48
48
  # Place it in a file
49
49
  write_scratch_file(destination, r, opt[:absolute_destination]) do |path, scratch|
50
- if files_are_same?(path, scratch) || (opt[:once] && File.exists?(path))
50
+ updated = File.exists? path
51
+ Embeds.copy_keeps(path, scratch)
52
+ if files_are_same?(path, scratch) || (opt[:once] && updated)
51
53
  FileUtils.rm scratch
52
54
  else
53
- updated = File.exists? path
54
55
  FileUtils.mv scratch, path
55
56
  STDOUT.write "#{updated ? :Updated : :Created} #{path.relative_to_project_root}\n".color(updated ? :white : :green) unless opt[:quiet]
56
57
  end
@@ -31,11 +31,13 @@ module Cog
31
31
  # @option opt [Fixnum] :occurrence (0) 0 for the first, 1 for the second, and so on
32
32
  # @yieldparam scanner [FileScanner] a file scanner
33
33
  # @yieldreturn [Object]
34
- # @return [Object] the return value of the block
34
+ # @return [Object,nil] the return value of the block, or +nil+ if the pattern was not found
35
35
  def self.scan(filename, pattern, opt={}, &block)
36
36
  s = new filename
37
- if s.read_until pattern, opt[:occurrence] || 0
38
- val = block.call s
37
+ val = if s.read_until pattern, opt[:occurrence] || 0
38
+ block.call s
39
+ else
40
+ nil
39
41
  end
40
42
  s.close
41
43
  val
@@ -18,7 +18,7 @@ module Cog
18
18
  # @param cogfile_path [String] path to the plugin Cogfile
19
19
  def initialize(cogfile_path)
20
20
  unless File.exists?(cogfile_path)
21
- raise Errors::InvalidPluginConfiguration.new(cogfile_path)
21
+ raise Errors::InvalidPluginConfiguration.new :cogfile => cogfile_path
22
22
  end
23
23
  @cogfile_path = File.expand_path cogfile_path
24
24
  @path = File.dirname @cogfile_path
@@ -1,5 +1,5 @@
1
1
  module Cog
2
2
  unless const_defined? :VERSION
3
- VERSION = '0.3.0'
3
+ VERSION = '0.3.1'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cog
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 0
10
- version: 0.3.0
9
+ - 1
10
+ version: 0.3.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Kevin Tonon
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-12-30 00:00:00 Z
18
+ date: 2013-01-05 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: gli