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.
- data/data/gem-new/default/meta.yaml +3 -0
- data/data/gem-new/default/skeleton/GEM_NAME.gemspec.erb +29 -0
- data/data/gem-new/default/skeleton/README.markdown.literal +21 -0
- data/data/gem-new/default/skeleton/README_DIRECTORIES +9 -0
- data/data/gem-new/default/skeleton/Rakefile +10 -0
- data/data/gem-new/default/skeleton/bin/.gitkeep +0 -0
- data/data/gem-new/default/skeleton/development/.gitkeep +0 -0
- data/data/gem-new/default/skeleton/documentation/.gitkeep +0 -0
- data/data/gem-new/default/skeleton/lib/.gitkeep +0 -0
- data/data/gem-new/default/skeleton/lib/REQUIRE_NAME.rb.erb +24 -0
- data/data/gem-new/default/skeleton/lib/REQUIRE_NAME/version.rb.erb +7 -0
- data/data/gem-new/default/skeleton/rake/lib/.gitkeep +0 -0
- data/data/gem-new/default/skeleton/rake/tasks/.gitkeep +0 -0
- data/data/gem-new/default/skeleton/test/.gitkeep +0 -0
- data/data/gem-new/gem-new-template/meta.yaml +3 -0
- data/data/gem-new/gem-new-template/skeleton/data/GEM_NAME/your_template_name/meta.yaml +3 -0
- data/data/gem-new/gem-new-template/skeleton/data/GEM_NAME/your_template_name/skeleton/.gitkeep +0 -0
- data/lib/gem/commands/new_command.rb +285 -0
- data/lib/gem/commands/new_command/blank_slate.rb +124 -0
- data/lib/gem/commands/new_command/configuration.rb +31 -0
- data/lib/gem/commands/new_command/erb_template.rb +193 -0
- data/lib/gem/commands/new_command/skeleton.rb +129 -0
- data/lib/gem/commands/new_command/string_replacer.rb +26 -0
- data/lib/gem/commands/new_command/version.rb +3 -0
- data/lib/rubygems_plugin.rb +4 -0
- metadata +70 -0
@@ -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,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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
data/data/gem-new/gem-new-template/skeleton/data/GEM_NAME/your_template_name/skeleton/.gitkeep
ADDED
File without changes
|
@@ -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
|
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: []
|