gem-new 0.1.0

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 (26) hide show
  1. data/data/gem-new/default/meta.yaml +3 -0
  2. data/data/gem-new/default/skeleton/GEM_NAME.gemspec.erb +29 -0
  3. data/data/gem-new/default/skeleton/README.markdown.literal +21 -0
  4. data/data/gem-new/default/skeleton/README_DIRECTORIES +9 -0
  5. data/data/gem-new/default/skeleton/Rakefile +10 -0
  6. data/data/gem-new/default/skeleton/bin/.gitkeep +0 -0
  7. data/data/gem-new/default/skeleton/development/.gitkeep +0 -0
  8. data/data/gem-new/default/skeleton/documentation/.gitkeep +0 -0
  9. data/data/gem-new/default/skeleton/lib/.gitkeep +0 -0
  10. data/data/gem-new/default/skeleton/lib/REQUIRE_NAME.rb.erb +24 -0
  11. data/data/gem-new/default/skeleton/lib/REQUIRE_NAME/version.rb.erb +7 -0
  12. data/data/gem-new/default/skeleton/rake/lib/.gitkeep +0 -0
  13. data/data/gem-new/default/skeleton/rake/tasks/.gitkeep +0 -0
  14. data/data/gem-new/default/skeleton/test/.gitkeep +0 -0
  15. data/data/gem-new/gem-new-template/meta.yaml +3 -0
  16. data/data/gem-new/gem-new-template/skeleton/data/GEM_NAME/your_template_name/meta.yaml +3 -0
  17. data/data/gem-new/gem-new-template/skeleton/data/GEM_NAME/your_template_name/skeleton/.gitkeep +0 -0
  18. data/lib/gem/commands/new_command.rb +285 -0
  19. data/lib/gem/commands/new_command/blank_slate.rb +124 -0
  20. data/lib/gem/commands/new_command/configuration.rb +31 -0
  21. data/lib/gem/commands/new_command/erb_template.rb +193 -0
  22. data/lib/gem/commands/new_command/skeleton.rb +129 -0
  23. data/lib/gem/commands/new_command/string_replacer.rb +26 -0
  24. data/lib/gem/commands/new_command/version.rb +3 -0
  25. data/lib/rubygems_plugin.rb +4 -0
  26. metadata +70 -0
@@ -0,0 +1,3 @@
1
+ ---
2
+ version: 1
3
+ includes: []
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = <%= gem_name.inspect %>
5
+ s.version = <%= version.to_s.inspect %>
6
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1")
7
+ s.authors = <%= author.inspect %>
8
+ s.date = <%= date.inspect %>
9
+ s.description = <<-DESCRIPTION.gsub(/^ /, '').chomp
10
+ <%= description && description.gsub(/^/,' ') %>
11
+ DESCRIPTION
12
+ s.summary = <<-SUMMARY.gsub(/^ /, '').chomp
13
+ <%= summary && summary.gsub(/^/,' ') %>
14
+ SUMMARY
15
+ s.email = <%= email.inspect %>
16
+ s.files =
17
+ Dir['bin/**/*'] +
18
+ Dir['lib/**/*'] +
19
+ Dir['rake/**/*'] +
20
+ Dir['test/**/*'] +
21
+ %w[
22
+ <%= "#{gem_name}.gemspec" %>
23
+ Rakefile
24
+ README.markdown
25
+ ]
26
+ s.require_paths = %w[lib]
27
+ s.rubygems_version = "1.3.1"
28
+ s.specification_version = 3
29
+ end
@@ -0,0 +1,21 @@
1
+ README
2
+ ======
3
+
4
+
5
+ Summary
6
+ -------
7
+ <%= summary %>
8
+
9
+
10
+ Installation
11
+ ------------
12
+ `gem install <%= gem_name %>`
13
+
14
+
15
+ Usage
16
+ -----
17
+
18
+
19
+ Description
20
+ -----------
21
+ <%= description %>
@@ -0,0 +1,9 @@
1
+ This readme informs you about the purpose of the directories
2
+
3
+ PROJECT/bin: Executable files
4
+ PROJECT/documentation: Prosa documentation of your project
5
+ PROJECT/development: Ideas and other things relevant for developing
6
+ PROJECT/lib: Your ruby library files
7
+ PROJECT/rake/lib: Libraries that are not part of your library, but used by your rake tasks
8
+ PROJECT/rake/tasks: Your rake tasks, using either .rb, .task or .rake as suffix
9
+ PROJECT/test: The tests for your library
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../rake/lib', __FILE__))
2
+ Dir.glob(File.expand_path('../rake/tasks/**/*.{rake,task,rb}', __FILE__)) do |task_file|
3
+ begin
4
+ require task_file
5
+ rescue LoadError => e
6
+ warn "Failed to load task file #{task_file}"
7
+ warn " #{e.class} #{e.message}"
8
+ warn " #{e.backtrace.first}"
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "gem-new"
5
+ s.version = "0.1.0"
6
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1")
7
+ s.authors = ["Stefan Rusterholz"]
8
+ s.date = "2011-06-10"
9
+ s.default_executable = "baretest"
10
+ s.description = <<-DESCRIPTION.chomp
11
+ Gem-new is a gem command plugin that allows you to easily create a new gem.
12
+ DESCRIPTION
13
+ s.summary = <<-SUMMARY.chomp
14
+ Create new gems easily
15
+ SUMMARY
16
+ s.email = "stefan.rusterholz@gmail.com"
17
+ s.files = %w[
18
+ lib/rubygems_plugin.rb
19
+ ]
20
+ s.homepage = "http://github.com/apeiros/gem-new"
21
+ s.require_paths = %w[lib]
22
+ s.rubygems_version = "1.3.1"
23
+ s.specification_version = 3
24
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require 'gem/version'
4
+
5
+ module <%= namespace %>
6
+ Version = Gem::Version.new(<%= version.to_s.inspect %>)
7
+ end
@@ -0,0 +1,3 @@
1
+ ---
2
+ version: 1
3
+ includes: []
@@ -0,0 +1,285 @@
1
+ require 'rubygems/command'
2
+ require 'gem/commands/new_command/configuration'
3
+ require 'gem/commands/new_command/skeleton'
4
+ require 'gem/commands/new_command/version'
5
+ require 'fileutils'
6
+ require 'open3'
7
+
8
+
9
+
10
+ class Gem::Commands::NewCommand < Gem::Command
11
+ if Gem::Specification.respond_to?(:find_all) then
12
+ plugin_gems = Gem::Specification.find_all { |spec| spec.name =~ /^gem-new/ }
13
+ else
14
+ plugin_gems = Gem::SourceIndex.from_installed_gems.find_name(/^gem-new-/)
15
+ end
16
+
17
+ ConfigVersion = 2
18
+ ConfigPath = File.join(Gem.user_home, '.gem', 'new', 'config')
19
+ UserTemplatesDir = [:user, nil, File.join(Gem.user_home, '.gem', 'new', 'templates')]
20
+ BundledTemplatesDir = [:bundled, nil, Gem.datadir('gem-new')]
21
+ PluginTemplatesDirs = plugin_gems.map { |spec|
22
+ spec.name
23
+ }.sort.map { |name|
24
+ gem name # load the gem, otherwise Gem.datadir is nil
25
+ [:gem, name, Gem.datadir(name)]
26
+ }
27
+ TemplateDirs = [UserTemplatesDir] + PluginTemplatesDirs + [BundledTemplatesDir]
28
+
29
+ def initialize
30
+ super 'new', "Create a new gem"
31
+ add_option('-l', '--list-templates', 'Show a list of all templates') do |value, opts|
32
+ opts[:list_templates] = value
33
+ end
34
+ add_option('-t', '--template TEMPLATE', 'Use TEMPLATE instead of the default template') do |value, opts|
35
+ opts[:template] = value
36
+ end
37
+ add_option('--variables', 'Show all available variables') do |value, opts|
38
+ opts[:variables] = value
39
+ end
40
+ end
41
+
42
+ def execute
43
+ if File.exist?(ConfigPath) then
44
+ if Configuration.new(ConfigPath).config_version < ConfigVersion then
45
+ FileUtils.mv(ConfigPath, "#{ConfigPath}.bak")
46
+ generate_default_config
47
+ puts "Your configuration has been moved to #{ConfigPath}.bak and an updated configuration has been created"
48
+ exit if prompt "Abort now in order to inspect the configuration (recommended)?"
49
+ end
50
+ else
51
+ generate_default_config
52
+ puts "A new configuration was generated in #{ConfigPath}"
53
+ exit if prompt "Abort now in order to inspect the configuration (recommended)?"
54
+ end
55
+
56
+ if options[:list_templates] then
57
+ list_templates
58
+ else
59
+ generate_gem
60
+ end
61
+ rescue
62
+ puts "#{$!.class} #{$!.message}", *$!.backtrace.first(5)
63
+ end
64
+
65
+ def arguments # :nodoc:
66
+ "GEMNAME The name of the gem to generate"
67
+ end
68
+
69
+ def usage # :nodoc:
70
+ "#{program_name} GEMNAME"
71
+ end
72
+
73
+ def defaults_str # :nodoc:
74
+ ""
75
+ end
76
+
77
+ def description # :nodoc:
78
+ <<-DESCRIPTION.gsub(/^ /,'').chomp
79
+ Creates the basic directories and files of a new gem for you.
80
+
81
+ TEMPLATES
82
+ You can provide custom templates in #{UserTemplatesDir}
83
+ To create a template named 'my_template', you'd create the following structure:
84
+ * #{UserTemplatesDir}/
85
+ * my_template/
86
+ * meta.yaml
87
+ * skeleton/
88
+ Within the 'skeleton' directory you place the files and directories for your skeleton.
89
+ Use `#{program_name} GEMNAME --variables` to see what variables are available for paths
90
+ and template-files.
91
+
92
+ CONFIGURATION
93
+ You can configure a couple of settings in #{ConfigPath}
94
+ DESCRIPTION
95
+ #'
96
+ end
97
+
98
+ private
99
+ def templates
100
+ #Dir[template_glob].map { |path| File.basename(path) }.uniq.sort
101
+ seen = {}
102
+ list = []
103
+ TemplateDirs.each do |source_type, source_name, dir|
104
+ Dir.glob(File.join(dir, '*')) do |path|
105
+ name = File.basename(path)
106
+ unless seen[name] then
107
+ list << [source_type, source_name, dir, name]
108
+ seen[name] = true
109
+ end
110
+ end
111
+ end
112
+
113
+ list
114
+ end
115
+
116
+ def list_templates
117
+ puts "List of available templates (to use with the -t TEMPLATE option):"
118
+
119
+ default = Configuration.new(ConfigPath).default_template
120
+ templates.sort_by { |source_type, source_name, dir, name|
121
+ name
122
+ }.each do |source_type, source_name, dir, name|
123
+ default_addon = (default == name) ? ', default' : ''
124
+ case source_type
125
+ when :gem then puts "- #{name} (from gem '#{source_name}'#{default_addon})"
126
+ when :bundled then puts "- #{name} (bundled with gem-new#{default_addon})"
127
+ when :user then puts "- #{name} (user template#{default_addon})"
128
+ else raise "Unknown source type #{source_type.inspect}"
129
+ end
130
+ end
131
+ end
132
+
133
+ def list_variables(variables)
134
+ path_vars = variables[:path_vars]
135
+ content_vars = variables[:content_vars]
136
+ longest_key = (path_vars.keys+content_vars.keys).map { |key| key.length }.max
137
+
138
+ puts "Path variables:"
139
+ path_vars.each do |key, value|
140
+ printf "%-*s %p\n", longest_key, key, value
141
+ end
142
+ puts "\nContent variables:"
143
+ content_vars.each do |key, value|
144
+ printf "%-*s %p\n", longest_key, key, value
145
+ end
146
+ end
147
+
148
+ def generate_gem
149
+ configuration = Configuration.new(ConfigPath)
150
+ gem_name = get_one_optional_argument
151
+ abort("Must provide a gem name") unless gem_name
152
+ gem_root = File.expand_path(Dir.getwd)
153
+ gem_path = File.join(gem_root, gem_name)
154
+ template_name = options[:template] || configuration.default_template
155
+ includes = [[template_name]]
156
+ skeletons = []
157
+ until includes.empty?
158
+ include_chain = includes.shift
159
+ skeleton_name = include_chain.last
160
+ skeleton_path = template_path(skeleton_name)
161
+ abort("No template named '#{skeleton_name}' found") unless skeleton_path
162
+ skeleton = Skeleton.new(skeleton_path)
163
+ new_includes = skeleton.includes
164
+ abort("Circular include found") unless (new_includes & include_chain).empty?
165
+ includes.concat(new_includes.map { |skeleton_name| include_chain+[skeleton_name] })
166
+ skeletons << skeleton
167
+ end
168
+
169
+ if File.exist?(gem_path) then
170
+ exit unless prompt("A directory '#{gem_path}' already exists, continue?", :no)
171
+ puts "Updating gem '#{gem_name}' in '#{gem_root}"
172
+ else
173
+ puts "Creating gem '#{gem_name}' in '#{gem_root}"
174
+ end
175
+
176
+ puts "Using the '#{template_name}' directory skeleton"
177
+
178
+ puts "", "Please enter the gem description, terminate with enter and ctrl-d"
179
+ description = $stdin.read
180
+ puts "", "Please enter the gem summary, terminate with enter and ctrl-d"
181
+ summary = $stdin.read
182
+ puts
183
+
184
+ now = Time.now
185
+ variables = {
186
+ :path_vars => symbolize_keys(configuration.path_variables).merge({
187
+ :GEM_NAME => gem_name,
188
+ :REQUIRE_NAME => gem_name,
189
+ :YEAR => now.year,
190
+ :MONTH => now.month,
191
+ :DAY => now.day,
192
+ :DATE => now.strftime("%Y-%m-%d"),
193
+ }),
194
+ :content_vars => symbolize_keys(configuration.content_variables).merge({
195
+ :gem_name => gem_name,
196
+ :require_name => gem_name,
197
+ :now => now,
198
+ :namespace => camelcase(gem_name),
199
+ :version => configuration.initial_version,
200
+ :description => description,
201
+ :summary => summary,
202
+ :date => now.strftime("%Y-%m-%d"),
203
+ }),
204
+ }
205
+
206
+ if options[:variables] then
207
+ list_variables(variables)
208
+ else
209
+ skeletons.reverse_each do |skeleton| # reverse_each?
210
+ puts "Template #{skeleton.name}"
211
+ skeleton.materialize(gem_path, variables) do |path, new_content|
212
+ interactive_materialization(path, new_content, configuration)
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ def interactive_materialization(path, new_content, configuration)
219
+ old_content = File.read(path)
220
+ return false if old_content == new_content
221
+
222
+ while result.nil?
223
+ print("File already exists, replace? ([N]o, [y]es, [d]iff) ")
224
+ $stdout.flush
225
+ case $stdin.gets
226
+ when nil then abort("Terminated")
227
+ when /^y(?:es)?$/i then return true
228
+ when /^n(?:o)?$/i then return false
229
+ when /^d(?:iff)?$/i then puts Open3.popen3(configuration.diff_tool % path) { |i,o,e| i.puts new_content; i.close; o.read }
230
+ else puts "Invalid reply"
231
+ end
232
+ end
233
+ end
234
+
235
+ def prompt(question, default=:yes)
236
+ print "#{question} #{default == :yes ? '([Y]es, [n]o)' : '([N]o, [y]es)'} "
237
+ $stdout.flush
238
+ if default == :yes then
239
+ $stdin.gets !~ /^n(?:o)?\b/i
240
+ else
241
+ $stdin.gets =~ /^y(?:es)?\b/i
242
+ end
243
+ end
244
+
245
+ def generate_default_config
246
+ yaml = <<-YAML.gsub(/^ /, '')
247
+ ---
248
+ # used to migrate configurations automatically
249
+ config_version: #{ConfigVersion}
250
+ # the template used without -t option
251
+ default_template: default
252
+ # the diff command used to show diffs on update, %s is the path to the old file
253
+ diff_tool: diff -y --label old --label new %s -
254
+ # whether a diff should be shown right away in updates, without asking first
255
+ auto_diff: true
256
+ # content variables are used for subsitution in template files, also see path_variables
257
+ content_variables:
258
+ author: #{ENV["USER"] || 'unknown author'}
259
+ #email: "your.email@address"
260
+ # content variables are used for subsitution in template paths, also see content_variables
261
+ path_variables: {}
262
+ YAML
263
+ FileUtils.mkdir_p(File.dirname(ConfigPath))
264
+ File.open(ConfigPath, 'wb') { |fh| fh.write(yaml) }
265
+ end
266
+
267
+ def template_glob(name='*')
268
+ "{#{TemplateDirs.map { |dir| dir.last }.join(',')}}/#{name}"
269
+ end
270
+
271
+ def template_path(name)
272
+ Dir.glob(template_glob(name)) do |path|
273
+ return path
274
+ end
275
+ nil
276
+ end
277
+
278
+ def symbolize_keys(hash)
279
+ Hash[hash.map{|k,v|[k.to_sym,v]}]
280
+ end
281
+
282
+ def camelcase(string)
283
+ string.gsub(/(?:^|_)([a-z])/) { |m| $1.upcase }
284
+ end
285
+ end
@@ -0,0 +1,124 @@
1
+ require 'stringio'
2
+
3
+
4
+ #--
5
+ # Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
6
+ # All rights reserved.
7
+
8
+ # Permission is granted for use, copying, modification, distribution,
9
+ # and distribution of modified versions of this work as long as the
10
+ # above copyright notice is included.
11
+ #
12
+ # Enhanced by Stefan Rusterholz (stefan.rusterholz@gmail.com)
13
+ #++
14
+
15
+ class Gem::Commands::NewCommand < Gem::Command
16
+ ######################################################################
17
+ # BlankSlate provides an abstract base class with no predefined
18
+ # methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
19
+ # BlankSlate is useful as a base class when writing classes that
20
+ # depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
21
+ #
22
+ class BlankSlate
23
+ class << self
24
+
25
+ # Hide the method named +name+ in the BlankSlate class. Don't
26
+ # hide +instance_eval+ or any method beginning with "__".
27
+ def hide(name)
28
+ verbosity = $VERBOSE
29
+ stderr = $stderr
30
+ $VERBOSE = nil
31
+ $stderr = StringIO.new
32
+
33
+ methods = instance_methods.map(&:to_sym)
34
+ if methods.include?(name.to_sym) and
35
+ name !~ /^(__|instance_eval)/
36
+ @hidden_methods ||= {}
37
+ @hidden_methods[name.to_sym] = instance_method(name)
38
+ undef_method name
39
+ end
40
+ ensure
41
+ $VERBOSE = verbosity
42
+ $stderr = stderr
43
+ end
44
+
45
+ def find_hidden_method(name)
46
+ @hidden_methods ||= {}
47
+ @hidden_methods[name] || superclass.find_hidden_method(name)
48
+ end
49
+
50
+ # Redefine a previously hidden method so that it may be called on a blank
51
+ # slate object.
52
+ def reveal(name)
53
+ hidden_method = find_hidden_method(name)
54
+ fail "Don't know how to reveal method '#{name}'" unless hidden_method
55
+ define_method(name, hidden_method)
56
+ end
57
+ end
58
+
59
+ instance_methods.each { |m| hide(m) }
60
+ end
61
+ end
62
+
63
+ ######################################################################
64
+ # Since Ruby is very dynamic, methods added to the ancestors of
65
+ # BlankSlate <em>after BlankSlate is defined</em> will show up in the
66
+ # list of available BlankSlate methods. We handle this by defining a
67
+ # hook in the Object and Kernel classes that will hide any method
68
+ # defined after BlankSlate has been loaded.
69
+ #
70
+ module Kernel
71
+ class << self
72
+ alias gemnew_blank_slate_method_added method_added
73
+
74
+ # Detect method additions to Kernel and remove them in the
75
+ # BlankSlate class.
76
+ def method_added(name)
77
+ result = gemnew_blank_slate_method_added(name)
78
+ return result if self != Kernel
79
+ Gem::Commands::NewCommand::BlankSlate.hide(name)
80
+ result
81
+ end
82
+ end
83
+ end
84
+
85
+ ######################################################################
86
+ # Same as above, except in Object.
87
+ #
88
+ class Object
89
+ class << self
90
+ alias gemnew_blank_slate_method_added method_added
91
+
92
+ # Detect method additions to Object and remove them in the
93
+ # BlankSlate class.
94
+ def method_added(name)
95
+ result = gemnew_blank_slate_method_added(name)
96
+ return result if self != Object
97
+ Gem::Commands::NewCommand::BlankSlate.hide(name)
98
+ result
99
+ end
100
+
101
+ def find_hidden_method(name)
102
+ nil
103
+ end
104
+ end
105
+ end
106
+
107
+ ######################################################################
108
+ # Also, modules included into Object need to be scanned and have their
109
+ # instance methods removed from blank slate. In theory, modules
110
+ # included into Kernel would have to be removed as well, but a
111
+ # "feature" of Ruby prevents late includes into modules from being
112
+ # exposed in the first place.
113
+ #
114
+ class Module
115
+ alias gemnew_blank_slate_original_append_features append_features
116
+ def append_features(mod)
117
+ result = gemnew_blank_slate_original_append_features(mod)
118
+ return result if mod != Object
119
+ instance_methods.each do |name|
120
+ Gem::Commands::NewCommand::BlankSlate.hide(name)
121
+ end
122
+ result
123
+ end
124
+ end
@@ -0,0 +1,31 @@
1
+ class Gem::Commands::NewCommand < Gem::Command
2
+ class Configuration
3
+ def initialize(config_path)
4
+ @config = YAML.load_file(config_path)
5
+ end
6
+
7
+ def diff_tool
8
+ @config['diff_tool']
9
+ end
10
+
11
+ def content_variables
12
+ @config['content_variables']
13
+ end
14
+
15
+ def path_variables
16
+ @config['path_variables']
17
+ end
18
+
19
+ def default_template
20
+ @config['default_template']
21
+ end
22
+
23
+ def config_version
24
+ @config['config_version']
25
+ end
26
+
27
+ def initial_version
28
+ "0.0.1"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,193 @@
1
+ require 'erb'
2
+ require 'gem/commands/new_command/blank_slate'
3
+
4
+
5
+
6
+ class Gem::Commands::NewCommand < Gem::Command
7
+
8
+ # = Indexing
9
+ # Author: Stefan Rusterholz
10
+ # Contact: contact@apeiros.me
11
+ # Version: 0.1.0
12
+ #
13
+ # = About
14
+ # A helper class for ERB, allows constructs like the one in the Synopsis to
15
+ # enable simple use of variables/methods in templates.
16
+ #
17
+ # = Synopsis
18
+ # tmpl = Templater.new("Hello <%= name %>!")
19
+ # tmpl.result(self, :name => 'world') # => 'Hello World!'
20
+ #
21
+ class ErbTemplate
22
+
23
+ # = Indexing
24
+ # Author: Stefan Rusterholz
25
+ # Contact: contact@apeiros.me
26
+ # Version: 0.1.0
27
+ #
28
+ # = About
29
+ # Similar to OpenStruct, but slightly optimized for create once, use once and
30
+ # giving diagnostics on exceptions/missing keys.
31
+ #
32
+ # = Synopsis
33
+ # tmpl = Variables.new(delegator, :variable => "content") { |exception|
34
+ # do_something_with(exception)
35
+ # }
36
+ #
37
+ class Variables < BlankSlate
38
+ # A proc for &on_error in SilverPlatter::Variables::new or SilverPlatter::Templater#result.
39
+ # Raises the error further on.
40
+ Raiser = proc { |e|
41
+ raise(e)
42
+ }
43
+
44
+ # A proc for &on_error in SilverPlatter::Variables.new or SilverPlatter::Templater#result.
45
+ # Inserts <<error_class: error_message>> in the place where the error
46
+ # occurred.
47
+ Teller = proc { |e|
48
+ "<<#{e.class}: #{e}>>"
49
+ }
50
+
51
+ # An empty Hash
52
+ EmptyHash = {}.freeze
53
+
54
+ # Regex to match setter method names
55
+ SetterPattern = /=\z/.freeze
56
+
57
+ # === Arguments
58
+ # * delegate: All method calls and undefined variables are delegated to this object as method call.
59
+ # * variables: A hash with variables in it, keys must be Symbols.
60
+ # * on_error_name: Instead of a block you can pass the name of an existing handler, e.g. :Raiser or :Teller.
61
+ # * &on_error: The block is yielded in case of an exception with the exception as argument
62
+ #
63
+ def initialize(delegate=nil, variables={}, on_error_name=nil, &on_error)
64
+ @delegate = delegate
65
+ @table = (@delegate ? Hash.new { |h,k| @delegate.send(k) } : EmptyHash).merge(variables)
66
+ if !on_error && on_error_name then
67
+ @on_error = self.class.const_get(on_error_name)
68
+ else
69
+ @on_error = on_error || Raiser
70
+ end
71
+ end
72
+
73
+ # All keys this Variables instance provides, if the include_delegate argument is true and
74
+ # the object to delegate to responds to __keys__, then it will add the keys of the delegate.
75
+ def __keys__(include_delegate=true)
76
+ @table.keys + ((include_delegate && @delegate.respond_to?(:__keys__)) ? @delegate.__keys__ : [])
77
+ end
78
+
79
+ def __binding__
80
+ binding
81
+ end
82
+
83
+ # See Object#respond_to?
84
+ def respond_to?(key)
85
+ @table.respond_to?(key) || (@delegate && @delegate.respond_to?(key)) || super
86
+ end
87
+
88
+ def method_missing(m, *args) # :nodoc:
89
+ argn = args.length
90
+ if argn.zero? && @table.has_key?(m) then
91
+ @table[m]
92
+ elsif argn == 1 && m.to_s =~ SetterPattern
93
+ @table[m] = args.first
94
+ elsif @delegate
95
+ @delegate.send(m, *args)
96
+ end
97
+ rescue => e
98
+ @on_error.call(e)
99
+ end
100
+
101
+ def inspect # :nodoc:
102
+ sprintf "#<%s:0x%08x @delegate=%s %s>",
103
+ self.class,
104
+ __id__,
105
+ @table.map { |k,v| "#{k}=#{v.inspect}" }.join(', '),
106
+ @delegate ? "#<%s:0x%08x ...>" % [@delegate.class, @delegate.object_id << 1] : "nil"
107
+ end
108
+ end
109
+
110
+ # Option defaults
111
+ Opt = {
112
+ :safe_level => nil,
113
+ :trim_mode => '%<>',
114
+ :eoutvar => '_erbout'
115
+ }
116
+
117
+ # binding method
118
+ Binder = Object.instance_method(:binding)
119
+ InstanceEvaler = Object.instance_method(:instance_eval)
120
+
121
+
122
+ # A proc for &on_error in SilverPlatter::Variables::new or SilverPlatter::Templater#result.
123
+ # Raises the error further on.
124
+ Raiser = proc { |e|
125
+ raise
126
+ }
127
+
128
+ # A proc for &on_error in SilverPlatter::Variables.new or SilverPlatter::Templater#result.
129
+ # Inserts <<error_class: error_message>> in the place where the error
130
+ # occurred.
131
+ Teller = proc { |e|
132
+ "<<#{e.class}: #{e}>>"
133
+ }
134
+
135
+ # The template string
136
+ attr_reader :string
137
+
138
+ # Like Templater.new, but instead of a template string, the path to the file
139
+ # containing the template. Sets :filename.
140
+ def self.file(path, opt=nil)
141
+ new(File.read(path), (opt || {}).merge(:filename => path))
142
+ end
143
+
144
+ def self.replace(template, variables, &on_error)
145
+ new(template).result(nil, variables, &on_error)
146
+ end
147
+
148
+ # ==== Arguments
149
+ # * string: The template string, it becomes frozen
150
+ # * opt: Option hash, keys:
151
+ # * :filename: The filename used for the evaluation (useful for error messages)
152
+ # * :safe_level: see ERB.new
153
+ # * :trim_mode: see ERB.new
154
+ # * :eoutvar: see ERB.new
155
+ def initialize(string, opt={})
156
+ opt, string = string, nil if string.kind_of?(Hash)
157
+ opt = Opt.merge(opt)
158
+ file = opt.delete(:filename)
159
+ @string = string.freeze
160
+ @erb = ERB.new(@string, *opt.values_at(:safe_level, :trim_mode, :eoutvar))
161
+ @erb.filename = file if file
162
+ end
163
+
164
+ # See Templater::Variables.new
165
+ # Returns the evaluated template. Default &on_error is the Templater::Raiser
166
+ # proc.
167
+ def result(delegate=nil, variables={}, on_error_name=nil, &on_error)
168
+ variables ||= {}
169
+ on_error ||= Raiser
170
+ variables = Variables.new(delegate, variables, on_error_name, &on_error)
171
+ @erb.result(variables.__binding__)
172
+ rescue NameError => e
173
+ raise NameError, e.message+" for #{self.inspect} with #{variables.inspect}", e.backtrace
174
+ end
175
+
176
+ def result_with(opt, &block)
177
+ variables = opt.delete(:variables) || {}
178
+ on_error = opt.delete(:on_error) || Raiser
179
+ on_error_name = opt.delete(:on_error_name) || Raiser
180
+ variables = Variables.new(delegate, variables, on_error_name, block, &on_error)
181
+ @erb.result(variables.__binding__)
182
+ rescue NameError => e
183
+ raise NameError, e.message+" for #{self.inspect} with #{variables.inspect}", e.backtrace
184
+ end
185
+
186
+ def inspect # :nodoc:
187
+ sprintf "#<%s:0x%x string=%s>",
188
+ self.class,
189
+ object_id << 1,
190
+ @string.inspect
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,129 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+ require 'gem/commands/new_command/string_replacer'
4
+ require 'gem/commands/new_command/erb_template'
5
+
6
+
7
+
8
+ class Gem::Commands::NewCommand < Gem::Command
9
+ class Skeleton
10
+ DefaultOptions = {
11
+ :verbose => false,
12
+ :silent => false,
13
+ :out => $stdout,
14
+ }
15
+ Processors = {}
16
+ Processor = Struct.new(:suffix, :name, :execute)
17
+
18
+ def self.register_processor(suffix, name, &execute)
19
+ Processors[suffix] = Processor.new(suffix, name, execute)
20
+ end
21
+
22
+ register_processor '.literal', 'Literal' do |template, variables|
23
+ template
24
+ end
25
+ register_processor '.erb', 'ERB' do |template, variables|
26
+ ErbTemplate.replace(template, variables)
27
+ end
28
+
29
+ attr_reader :name
30
+ attr_reader :meta
31
+ attr_reader :base_path
32
+ attr_reader :source_slice
33
+ attr_reader :directories
34
+ attr_reader :files
35
+
36
+ def initialize(path, options=nil)
37
+ @name = File.basename(path)
38
+ @options = options ? DefaultOptions.merge(options) : DefaultOptions.dup
39
+ @meta_path = File.join(path, 'meta.yaml')
40
+ @meta = File.exist?(@meta_path) ? YAML.load_file(@meta_path) : {}
41
+ @base_path = path
42
+ @source_slice = (path.length+10)..-1
43
+ contents = Dir[File.join(path, "skeleton", "**", "{*,.gitkeep}")].sort
44
+ @directories, @files = contents.partition { |path|
45
+ File.directory?(path)
46
+ }
47
+ @verbose = @options.delete(:verbose)
48
+ @silent = @options.delete(:silent)
49
+ @out = @options.delete(:out)
50
+ raise ArgumentError, "Unknown options: #{@options.keys.join(', ')}" unless @options.empty?
51
+ end
52
+
53
+ def includes
54
+ @meta['includes'] || []
55
+ end
56
+
57
+ def materialize(in_path, env={}, &on_collision)
58
+ target_slice = (in_path.length+1)..-1
59
+ path_replacer = StringReplacer.new(env[:path_vars] || {})
60
+ content_vars = env[:content_vars] || {}
61
+
62
+ unless File.exist?(in_path) then
63
+ info "Creating root '#{in_path}'"
64
+ FileUtils.mkdir_p(in_path)
65
+ end
66
+
67
+ if @directories.empty? then
68
+ info "No directories to create"
69
+ else
70
+ info "Creating directories"
71
+ @directories.each do |source_dir_path|
72
+ target_dir_path = source_to_target_path(source_dir_path, in_path, path_replacer)
73
+ info " #{target_dir_path[target_slice]}"
74
+ FileUtils.mkdir_p(target_dir_path)
75
+ end
76
+ end
77
+
78
+ if @files.empty? then
79
+ info "No files to create"
80
+ else
81
+ info "Creating files"
82
+ @files.each do |source_file_path|
83
+ target_file_path, processors = source_to_target_path_and_processors(source_file_path, in_path, path_replacer)
84
+ content = processors.inject(File.read(source_file_path)) { |data, processor|
85
+ processor.execute.call(data, content_vars)
86
+ }
87
+ info " #{target_file_path[target_slice]}"
88
+ if !File.exist?(target_file_path) || (block_given? && yield(target_file_path, content)) then
89
+ File.open(target_file_path, 'wb') do |fh|
90
+ fh.write(content)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def source_to_target_path(source_path, target_dir, replacer)
98
+ File.join(target_dir, replacer.replace(source_path[@source_slice]))
99
+ end
100
+
101
+ def source_to_target_path_and_processors(source_path, target_dir, replacer)
102
+ replaced_source = replacer.replace(source_path[@source_slice])
103
+ extname = File.extname(replaced_source)
104
+ processors = []
105
+ processed_source = replaced_source
106
+
107
+ if processor = Processors[extname] then
108
+ processors << processor
109
+ processed_source = processed_source.chomp(extname)
110
+ end
111
+ target_path = File.join(target_dir, processed_source)
112
+ # while processor = Processors[extname]
113
+ # processors << processor
114
+ # processed_source = File.basename(processed_source, extname)
115
+ # extname = File.extname(processed_source)
116
+ # end
117
+
118
+ [target_path, processors]
119
+ end
120
+
121
+ private
122
+ def info(msg)
123
+ @out.puts msg unless @silent
124
+ end
125
+ def debug(msg)
126
+ @out.puts msg if @verbose
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ class Gem::Commands::NewCommand < Gem::Command
4
+ class StringReplacer
5
+ def self.replace(string, variables)
6
+ new(variables).replace(string)
7
+ end
8
+
9
+ def initialize(variables)
10
+ @variables = Hash[variables.map { |k,v| [k.to_s, v.to_s] }] # convert keys and values to strings
11
+ @pattern = Regexp.union(@variables.keys.map { |var| Regexp.escape(var) })
12
+ end
13
+
14
+ begin
15
+ "hi".gsub(/./u, {})
16
+ rescue TypeError # ruby 1.8
17
+ def replace(string)
18
+ string.gsub(@pattern) { |m| @variables[m] }
19
+ end
20
+ else # ruby 1.9
21
+ def replace(string)
22
+ string.gsub(@pattern, @variables)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ class Gem::Commands::NewCommand < Gem::Command
2
+ VERSION = Gem::Version.new("0.1.0")
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems/command_manager'
2
+ require 'gem/commands/new_command'
3
+
4
+ Gem::CommandManager.instance.register_command :new
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gem-new
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stefan Rusterholz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-06-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! ' Gem-new is a gem command plugin that allows you to easily create
15
+ a new gem.'
16
+ email: stefan.rusterholz@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - data/gem-new/default/meta.yaml
22
+ - data/gem-new/default/skeleton/GEM_NAME.gemspec.erb
23
+ - data/gem-new/default/skeleton/lib/REQUIRE_NAME/version.rb.erb
24
+ - data/gem-new/default/skeleton/lib/REQUIRE_NAME.rb.erb
25
+ - data/gem-new/default/skeleton/Rakefile
26
+ - data/gem-new/default/skeleton/README.markdown.literal
27
+ - data/gem-new/default/skeleton/README_DIRECTORIES
28
+ - data/gem-new/gem-new-template/meta.yaml
29
+ - data/gem-new/gem-new-template/skeleton/data/GEM_NAME/your_template_name/meta.yaml
30
+ - data/gem-new/default/skeleton/bin/.gitkeep
31
+ - data/gem-new/default/skeleton/development/.gitkeep
32
+ - data/gem-new/default/skeleton/documentation/.gitkeep
33
+ - data/gem-new/default/skeleton/lib/.gitkeep
34
+ - data/gem-new/default/skeleton/rake/lib/.gitkeep
35
+ - data/gem-new/default/skeleton/rake/tasks/.gitkeep
36
+ - data/gem-new/default/skeleton/test/.gitkeep
37
+ - data/gem-new/gem-new-template/skeleton/data/GEM_NAME/your_template_name/skeleton/.gitkeep
38
+ - lib/gem/commands/new_command/blank_slate.rb
39
+ - lib/gem/commands/new_command/configuration.rb
40
+ - lib/gem/commands/new_command/erb_template.rb
41
+ - lib/gem/commands/new_command/skeleton.rb
42
+ - lib/gem/commands/new_command/string_replacer.rb
43
+ - lib/gem/commands/new_command/version.rb
44
+ - lib/gem/commands/new_command.rb
45
+ - lib/rubygems_plugin.rb
46
+ homepage: http://github.com/apeiros/gem-new
47
+ licenses: []
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>'
62
+ - !ruby/object:Gem::Version
63
+ version: 1.3.1
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.5
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Create new gems easily
70
+ test_files: []