gem-new 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []