cog 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/cog CHANGED
@@ -39,7 +39,7 @@ end
39
39
  desc 'Manage project generators'
40
40
  command [:generator, :gen] do |c|
41
41
 
42
- c.default_command :list
42
+ c.default_command :run
43
43
 
44
44
  c.desc 'List project generators'
45
45
  c.command :list do |sub|
@@ -72,6 +72,8 @@ command [:generator, :gen] do |c|
72
72
  c.arg_name 'name'
73
73
  c.command :run do |sub|
74
74
  sub.action do |gopt, opt, args|
75
+ raise Cog::Errors::ActionRequiresProject.new('run generator') unless Cog::Config.instance.project?
76
+ Cog::Project.gather_cog_directives
75
77
  args = Cog::Controllers::GeneratorController.list if args.empty?
76
78
  args.each do |name|
77
79
  Cog::Controllers::GeneratorController.run name
data/lib/cog.rb CHANGED
@@ -4,6 +4,7 @@ require 'cog/errors'
4
4
  require 'cog/generator'
5
5
  require 'cog/helpers'
6
6
  require 'cog/languages'
7
+ require 'cog/project'
7
8
  require 'cog/version'
8
9
 
9
10
  # +cog+ is a command line utility that makes it a bit easier to organize a project
@@ -30,7 +30,7 @@ module Cog
30
30
  # @return [Languages::Lanugage] target language which should be used when creating generators, and no language is explicitly specified
31
31
  attr_accessor :target_language
32
32
 
33
- # @return [String] the target language which is currently active
33
+ # @return [Languages::Language] language which is active in the current context
34
34
  def active_language
35
35
  @active_languages.last
36
36
  end
@@ -146,6 +146,14 @@ module Cog
146
146
  end
147
147
  end
148
148
 
149
+ # @param ext [String] the file extension
150
+ # @return [Languages::Language, nil] the language for the given extension
151
+ def language_for_extension(ext)
152
+ ext = ext.to_s.downcase.to_sym
153
+ lang_id = @language_extension_map[ext]
154
+ Languages.get_language lang_id unless lang_id.nil?
155
+ end
156
+
149
157
  # @return [Array<LangInfo>] current configuration of supported languages
150
158
  def language_summary
151
159
  summary = {}
@@ -64,5 +64,9 @@ module Cog
64
64
  "scope stack underflow: this can happen if you have too many *_end calls in a template"
65
65
  end
66
66
 
67
+ define_error :SnippetExpansionUnterminated, 'location' do
68
+ "a snippet expansion in the given file is missing the 'cog: }' terminator"
69
+ end
70
+
67
71
  end
68
72
  end
@@ -72,5 +72,17 @@ module Cog
72
72
  nil
73
73
  end
74
74
 
75
+ # Provide a value for the snippet with the given key
76
+ # @param key [String] a unique identifier for the snippet
77
+ # @yield The return value of the provided block will be used to expand the snippet
78
+ # @return [nil]
79
+ def snippet(key, &block)
80
+ Project.snippet_directives(key) do |filename, index|
81
+ if Project.update_snippet_expansion key, filename, index, block.call
82
+ STDOUT.write "Updated #{filename.relative_to_project_root} - #{(index + 1).ordinalize} occurrence of snippet '#{key}'\n".color :white
83
+ end
84
+ end
85
+ end
86
+
75
87
  end
76
88
  end
@@ -15,6 +15,7 @@ module Cog
15
15
  :hpp => 'c++',
16
16
  :java => 'java',
17
17
  :js => 'javascript',
18
+ :pro => 'qt',
18
19
  :py => 'python',
19
20
  :rb => 'ruby',
20
21
  }
@@ -24,6 +25,7 @@ module Cog
24
25
  'c++' => 'c_plus_plus',
25
26
  'c#' => 'c_sharp',
26
27
  'javascript' => 'java_script',
28
+ 'qt' => 'qt_project',
27
29
  }
28
30
 
29
31
  # @return [Hash] a mapping of language identifiers to lists of aliases
@@ -5,12 +5,12 @@ module Cog
5
5
 
6
6
  class CLanguage < Language
7
7
 
8
- # @param text [String] some text which should be rendered as a comment
9
- # @return [String] a comment appropriate for this language
10
- def comment(text)
11
- "/*\n#{text}\n */"
8
+ # @param nested_pattern [String] regular expression pattern (as a string) to embed in the regular expression which matches one line comments in this language
9
+ # @return [Regexp] pattern for matching one line comments in this language
10
+ def comment_pattern(nested_pattern)
11
+ /^\s*(?:\/\/|\/\*)\s*#{nested_pattern}\s*(?:\*\/)?\s*$/i
12
12
  end
13
-
13
+
14
14
  # @return [Array<String>] a set of extensions needed to define a module in this language
15
15
  def module_extensions
16
16
  [:c, :h]
@@ -38,6 +38,16 @@ module Cog
38
38
  "#endif // #{name}"
39
39
  end
40
40
 
41
+ protected
42
+
43
+ def one_line_comment(text)
44
+ "// #{text}"
45
+ end
46
+
47
+ def multi_line_comment(text)
48
+ "/*\n#{text}\n */"
49
+ end
50
+
41
51
  end
42
52
  end
43
53
  end
@@ -4,11 +4,21 @@ module Cog
4
4
  # Interface that must be supported by all +cog+ language helpers.
5
5
  # This base class corresponds to a plain text language.
6
6
  class Language
7
+
8
+ # @param nested_pattern [String] regular expression pattern (as a string) to embed in the regular expression which matches one line comments in this language
9
+ # @return [Regexp] pattern for matching one line comments in this language
10
+ def comment_pattern(nested_pattern)
11
+ /^\s*#{nested_pattern}\s$/
12
+ end
7
13
 
8
14
  # @param text [String] some text which should be rendered as a comment
9
15
  # @return [String] a comment appropriate for this language
10
16
  def comment(text)
11
- text
17
+ if text =~ /\n/
18
+ multi_line_comment text
19
+ else
20
+ one_line_comment text
21
+ end
12
22
  end
13
23
 
14
24
  # @return [Array<String>] a set of extensions needed to define a module in this language
@@ -46,6 +56,18 @@ module Cog
46
56
  ""
47
57
  end
48
58
 
59
+ protected
60
+
61
+ def one_line_comment(text)
62
+ text
63
+ end
64
+
65
+ def multi_line_comment(text)
66
+ text.split("\n").collect do |line|
67
+ one_line_comment line
68
+ end.join "\n"
69
+ end
70
+
49
71
  end
50
72
  end
51
73
  end
@@ -5,12 +5,12 @@ module Cog
5
5
 
6
6
  class PythonLanguage < Language
7
7
 
8
- # @param text [String] some text which should be rendered as a comment
9
- # @return [String] a comment appropriate for this language
10
- def comment(text)
11
- "'''\n#{text}\n'''"
8
+ # @param nested_pattern [String] regular expression pattern (as a string) to embed in the regular expression which matches one line comments in this language
9
+ # @return [Regexp] pattern for matching one line comments in this language
10
+ def comment_pattern(nested_pattern)
11
+ /^\s*\#\s*#{nested_pattern}\s*$/i
12
12
  end
13
-
13
+
14
14
  # @return [Array<String>] a set of extensions needed to define a module in this language
15
15
  def module_extensions
16
16
  [:py]
@@ -36,6 +36,16 @@ module Cog
36
36
  ""
37
37
  end
38
38
 
39
+ protected
40
+
41
+ def one_line_comment(text)
42
+ "# #{text}"
43
+ end
44
+
45
+ def multi_line_comment(text)
46
+ "'''\n#{text}\n'''"
47
+ end
48
+
39
49
  end
40
50
  end
41
51
  end
@@ -0,0 +1,47 @@
1
+ require 'cog/languages/language'
2
+
3
+ module Cog
4
+ module Languages
5
+
6
+ class QtProjectLanguage < Language
7
+
8
+ # @param nested_pattern [String] regular expression pattern (as a string) to embed in the regular expression which matches one line comments in this language
9
+ # @return [Regexp] pattern for matching one line comments in this language
10
+ def comment_pattern(nested_pattern)
11
+ /^\s*\#\s*#{nested_pattern}\s*$/i
12
+ end
13
+
14
+ # @return [Array<String>] a set of extensions needed to define a module in this language
15
+ def module_extensions
16
+ [:pro]
17
+ end
18
+
19
+ # @return [String] ignored for Qt Project files
20
+ def named_scope_begin(name)
21
+ ""
22
+ end
23
+
24
+ # @return [String] ignored for Qt Project files
25
+ def named_scope_end(name)
26
+ ""
27
+ end
28
+
29
+ # @return [String] ignored for Qt Project files
30
+ def include_guard_begin(name)
31
+ ""
32
+ end
33
+
34
+ # @return [String] ignored for Qt Project files
35
+ def include_guard_end(name)
36
+ ""
37
+ end
38
+
39
+ protected
40
+
41
+ def one_line_comment(text)
42
+ "# #{text}"
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -5,12 +5,12 @@ module Cog
5
5
 
6
6
  class RubyLanguage < Language
7
7
 
8
- # @param text [String] some text which should be rendered as a comment
9
- # @return [String] a comment appropriate for this language
10
- def comment(text)
11
- "=begin\n#{text}\n=end"
8
+ # @param nested_pattern [String] regular expression pattern (as a string) to embed in the regular expression which matches one line comments in this language
9
+ # @return [Regexp] pattern for matching one line comments in this language
10
+ def comment_pattern(nested_pattern)
11
+ /^\s*\#\s*#{nested_pattern}\s*$/i
12
12
  end
13
-
13
+
14
14
  # @return [Array<String>] a set of extensions needed to define a module in this language
15
15
  def module_extensions
16
16
  [:rb]
@@ -38,6 +38,16 @@ module Cog
38
38
  ""
39
39
  end
40
40
 
41
+ protected
42
+
43
+ def one_line_comment(text)
44
+ "# #{text}"
45
+ end
46
+
47
+ def multi_line_comment(text)
48
+ "=begin\n#{text}\n=end"
49
+ end
50
+
41
51
  end
42
52
  end
43
53
  end
@@ -0,0 +1,207 @@
1
+ require 'cog/config'
2
+
3
+ module Cog
4
+
5
+ # Methods for querying and manipulating project files
6
+ module Project
7
+
8
+ # Search through all project files for cog directives, and remember them so that generators can refer to them later
9
+ def gather_cog_directives
10
+ @snippets ||= {}
11
+ @snippet_pattern ||= "cog\\s*:\\s*snippet\\s*\\(\\s*(.*?)\\s*\\)(\\s*\\{)?"
12
+ exts = Config.instance.language_summary.collect(&:extensions).flatten
13
+ sources = Dir.glob "#{Config.instance.project_source_path}/**/*.{#{exts.join ','}}"
14
+ sources.each do |filename|
15
+ ext = File.extname(filename).slice(1..-1)
16
+ lang = Config.instance.language_for_extension ext
17
+ w = File.read filename
18
+ w.scan(lang.comment_pattern(@snippet_pattern)) do |m|
19
+ key = m[0]
20
+ @snippets[key] ||= {}
21
+ @snippets[key][filename] ||= 0
22
+ @snippets[key][filename] += 1
23
+ end
24
+ end
25
+ end
26
+
27
+ # @param key [String] snippet key for which to find directive occurrences
28
+ # @yieldparam filename [String] name of the file in which the snippet occurred
29
+ # @yieldparam index [Fixnum] occurrence index of the snippet, 0 for the first occurrence, 1 for the second, and so on
30
+ def snippet_directives(key)
31
+ x = @snippets[key]
32
+ unless x.nil?
33
+ x.keys.sort.each do |filename|
34
+ x[filename].times do |index|
35
+ yield filename, index
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # @param key [String] unique identifier for the snippet
42
+ # @param filename [String] file in which to look for the snippet
43
+ # @param index [Fixnum] occurrence of the snippet. 0 for first, 1 for second, ...
44
+ # @param value [String] expansion value to use as the update
45
+ # @return [Boolean] whether or not the expansion was updated
46
+ def update_snippet_expansion(key, filename, index, value)
47
+ value = value.rstrip
48
+ ext = File.extname(filename).slice(1..-1)
49
+ lang = Config.instance.language_for_extension ext
50
+
51
+ snip_pattern = lang.comment_pattern("cog\\s*:\\s*snippet\\s*\\(\\s*(#{key})\\s*\\)(?:\\s*([{]))?")
52
+ end_pattern = lang.comment_pattern("cog\\s*:\\s*[}]")
53
+ not_end_pattern = lang.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? # snippet not found
58
+ false
59
+ elsif match[2] == '{' # snippet already expanded
60
+ unless s.capture_until end_pattern, :but_not => not_end_pattern
61
+ raise Errors::SnippetExpansionUnterminated.new "#{filename.relative_to_project_root}:#{s.marked_line_number}"
62
+ end
63
+ s.replace_captured_text(value + "\n") if value != s.captured_text
64
+ else # snippet not yet expanded
65
+ snip_line = lang.comment "cog: snippet(#{match[1]}) {"
66
+ value = [snip_line, value, lang.comment("cog: }") + "\n"]
67
+ s.insert_at_mark value.join("\n")
68
+ end
69
+ end
70
+ s.close
71
+ updated
72
+ end
73
+
74
+ # Helper for scanning files for snippet expansions
75
+ class FileScanner
76
+
77
+ def initialize(filename)
78
+ @filename = filename
79
+ @f = File.open filename, 'r'
80
+ @lines = []
81
+ @mark = nil
82
+ @cap_begin_pos = nil
83
+ @cap_end_pos = nil
84
+ end
85
+
86
+ # Closes the scanned file, if it is not already closed
87
+ def close
88
+ @f.close unless @f.closed?
89
+ end
90
+
91
+ # Remember this position. A later call to insert_at_mark will insert at this marked position
92
+ # @return [nil]
93
+ def mark!
94
+ @mark = @f.pos
95
+ @marked_line_number = @f.lineno
96
+ end
97
+
98
+ def unmark!
99
+ @mark = nil
100
+ @marked_line_number = -1
101
+ end
102
+
103
+ # @return [Fixnum] line number where mark! was called or -1 if mark! was not called
104
+ attr_reader :marked_line_number
105
+
106
+ # Advances the file until the (n+1)th occurence of the given pattern is encountered, or the end of the file is reached
107
+ # @param pattern [Regexp] a pattern to test for
108
+ # @param n [Fixnum] 0 for the first, 1 for the second, and so on
109
+ # @return [MatchData, nil] the match object if the pattern was found
110
+ def read_until(pattern, n=0)
111
+ i = 0
112
+ mark!
113
+ while (line = @f.readline) && i <= n
114
+ if m = pattern.match(line)
115
+ return m if i == n
116
+ i += 1
117
+ end
118
+ mark!
119
+ end
120
+ rescue EOFError
121
+ unmark!
122
+ nil
123
+ end
124
+
125
+ # Advances the file by one line
126
+ # @param pattern [Regexp] a pattern to test for
127
+ # @return [Boolean] whether or not the next line matched the pattern
128
+ def readline_matches?(pattern)
129
+ !!(@f.readline =~ pattern)
130
+ rescue EOFError
131
+ false
132
+ end
133
+
134
+ # Advances the file until the given pattern is encountered and captures lines as it reads. The line which matches the pattern will not be captured. Captured lines can later be recovered with captured_text
135
+ # @param pattern [Regexp] a pattern to test for
136
+ # @option opt [Array<Regexp>, Regexp] :but_not (nil) if a line matching any of the provided patterns is found before the desired pattern :bad_pattern_found will be thrown
137
+ # @return [Boolean] whether or not the pattern was found
138
+ def capture_until(pattern, opt={})
139
+ @cap_begin_pos = @f.pos
140
+ but_not = opt[:but_not] || []
141
+ but_not = [but_not] unless but_not.is_a?(Array)
142
+ while line = @f.readline
143
+ but_not.each do |bad_pattern|
144
+ if bad_pattern =~ line
145
+ return false
146
+ end
147
+ end
148
+ return true if line =~ pattern
149
+ @lines << line
150
+ @cap_end_pos = @f.pos
151
+ end
152
+ rescue EOFError
153
+ false
154
+ end
155
+
156
+ # @return [String] text captured during capture_until. The last newline is stripped
157
+ def captured_text
158
+ x = @lines.join('')
159
+ x = x.slice(0..-2) if x =~ /\n$/
160
+ x
161
+ end
162
+
163
+ # @param value [String] value to replace the captured_text with
164
+ # @return [Boolean] whether or not the operation succeeded
165
+ def replace_captured_text(value)
166
+ return false if @cap_begin_pos.nil? || @cap_end_pos.nil?
167
+ @f.seek 0
168
+ tmp.write @f.read(@cap_begin_pos)
169
+ tmp.write value
170
+ @f.seek @cap_end_pos
171
+ tmp.write @f.read
172
+ tmp.close
173
+ close
174
+ FileUtils.mv tmp_filename, @filename
175
+ true
176
+ end
177
+
178
+ # @param value [String] value to be inserted into the file at the current position
179
+ # @return [Boolean] whether or not the operation succeeded
180
+ def insert_at_mark(value)
181
+ return false if @mark.nil?
182
+ @f.seek 0
183
+ tmp.write @f.read(@mark)
184
+ tmp.write value
185
+ @f.readline # discard original
186
+ tmp.write @f.read
187
+ tmp.close
188
+ close
189
+ FileUtils.mv tmp_filename, @filename
190
+ true
191
+ end
192
+
193
+ private
194
+
195
+ def tmp_filename
196
+ @tmp_filename ||= "#{@filename}.tmp"
197
+ end
198
+
199
+ def tmp
200
+ @tmp ||= File.open(tmp_filename, 'w')
201
+ end
202
+ end
203
+
204
+ extend self # Singleton
205
+ end
206
+
207
+ end
@@ -10,8 +10,8 @@ module Cog
10
10
  attr_accessor :tools
11
11
 
12
12
  # @param path_to_cl_app [String] path
13
- def initialize(path_to_cl_app)
14
- @cog = File.expand_path path_to_cl_app
13
+ def initialize
14
+ @cog = File.expand_path File.join(File.dirname(__FILE__), '..', '..', '..', 'bin', 'cog')
15
15
  @tools = []
16
16
  end
17
17
 
@@ -1,5 +1,5 @@
1
1
  module Cog
2
2
  unless const_defined? :VERSION
3
- VERSION = '0.1.4'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -1,14 +1,10 @@
1
1
  require 'cog'
2
+ include Cog::Generator
2
3
 
3
- class <%= camelized %>
4
- include Cog::Generator
5
-
6
- def generate
7
- @class = '<%= name.gsub ' ', '' %>'
4
+ # Setup the template context
5
+ @class = '<%= name.gsub ' ', '' %>'
6
+
7
+ # Render the templates
8
8
  <% @language.module_extensions.each do |ext| %>
9
- stamp '<%= name %>.<%= ext %>', 'generated_<%= name %>.<%= ext %>'
9
+ stamp '<%= name %>.<%= ext %>', 'generated_<%= name %>.<%= ext %>'
10
10
  <% end %>
11
- end
12
- end
13
-
14
- <%= camelized %>.new.generate
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: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 4
10
- version: 0.1.4
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
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-11-14 00:00:00 Z
18
+ date: 2012-12-09 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: gli
@@ -162,8 +162,10 @@ files:
162
162
  - lib/cog/languages/java_script_language.rb
163
163
  - lib/cog/languages/language.rb
164
164
  - lib/cog/languages/python_language.rb
165
+ - lib/cog/languages/qt_project_language.rb
165
166
  - lib/cog/languages/ruby_language.rb
166
167
  - lib/cog/languages.rb
168
+ - lib/cog/project.rb
167
169
  - lib/cog/spec_helpers/matchers/match_maker.rb
168
170
  - lib/cog/spec_helpers/matchers.rb
169
171
  - lib/cog/spec_helpers/runner.rb