File: //usr/lib/ruby/site_ruby/1.8/puppet/face/module/generate.rb
Puppet::Face.define(:module, '1.0.0') do
action(:generate) do
summary "Generate boilerplate for a new module."
description <<-EOT
Generates boilerplate for a new module by creating the directory
structure and files recommended for the Puppet community's best practices.
A module may need additional directories beyond this boilerplate
if it provides plugins, files, or templates.
EOT
returns "Array of Pathname objects representing paths of generated files."
examples <<-EOT
Generate a new module in the current directory:
$ puppet module generate puppetlabs-ssh
We need to create a metadata.json file for this module. Please answer the
following questions; if the question is not applicable to this module, feel free
to leave it blank.
Puppet uses Semantic Versioning (semver.org) to version modules.
What version is this module? [0.1.0]
-->
Who wrote this module? [puppetlabs]
-->
What license does this module code fall under? [Apache 2.0]
-->
How would you describe this module in a single sentence?
-->
Where is this module's source code repository?
-->
Where can others go to learn more about this module?
-->
Where can others go to file issues about this module?
-->
----------------------------------------
{
"name": "puppetlabs-ssh",
"version": "0.1.0",
"author": "puppetlabs",
"summary": null,
"license": "Apache 2.0",
"source": "",
"project_page": null,
"issues_url": null,
"dependencies": [
{
"name": "puppetlabs-stdlib",
"version_requirement": ">= 1.0.0"
}
]
}
----------------------------------------
About to generate this metadata; continue? [n/Y]
-->
Notice: Generating module at /Users/username/Projects/puppet/puppetlabs-ssh...
Notice: Populating ERB templates...
Finished; module generated in puppetlabs-ssh.
puppetlabs-ssh/manifests
puppetlabs-ssh/manifests/init.pp
puppetlabs-ssh/metadata.json
puppetlabs-ssh/README.md
puppetlabs-ssh/spec
puppetlabs-ssh/spec/spec_helper.rb
puppetlabs-ssh/tests
puppetlabs-ssh/tests/init.pp
EOT
option "--skip-interview" do
summary "Bypass the interactive metadata interview"
description <<-EOT
Do not attempt to perform a metadata interview. Primarily useful for automatic
execution of `puppet module generate`.
EOT
end
arguments "<name>"
when_invoked do |name, options|
# Since we only want to interview if it's being rendered to the console
# (i.e. when invoked with `puppet module generate`), we can't do any work
# here in the when_invoked block. The result of this block is then
# passed to each renderer, which will handle it appropriately; by
# returning a simple message like this, every renderer will simply output
# the string.
# Our `when_rendering :console` handler will ignore this value and
# actually generate the module.
#
# All this is necessary because it is not possible at this point in time
# to know what the destination of the output is.
"This format is not supported by this action."
end
when_rendering :console do |_, name, options|
Puppet::ModuleTool.set_option_defaults options
begin
# A default dependency for all newly generated modules is being
# introduced as a substitute for the comments we used to include in the
# Modulefile. While introducing a default dependency is less than
# perfectly desirable, the cost is low, and the syntax is obtuse enough
# to justify its inclusion.
metadata = Puppet::ModuleTool::Metadata.new.update(
'name' => name,
'version' => '0.1.0',
'dependencies' => [
{ 'name' => 'puppetlabs-stdlib', 'version_requirement' => '>= 1.0.0' }
]
)
rescue ArgumentError
msg = "Could not generate directory #{name.inspect}, you must specify a dash-separated username and module name."
raise ArgumentError, msg, $!.backtrace
end
dest = Puppet::ModuleTool::Generate.destination(metadata)
result = Puppet::ModuleTool::Generate.generate(metadata, options[:skip_interview])
path = dest.relative_path_from(Pathname.pwd)
puts "Finished; module generated in #{path}."
result.join("\n")
end
end
end
module Puppet::ModuleTool::Generate
module_function
def generate(metadata, skip_interview = false)
interview(metadata) unless skip_interview
destination = duplicate_skeleton(metadata)
all_files = destination.basename + '**/*'
return Dir[all_files.to_s]
end
def interview(metadata)
puts "We need to create a metadata.json file for this module. Please answer the"
puts "following questions; if the question is not applicable to this module, feel free"
puts "to leave it blank."
begin
puts
puts "Puppet uses Semantic Versioning (semver.org) to version modules."
puts "What version is this module? [#{metadata.version}]"
metadata.update 'version' => user_input(metadata.version)
rescue
Puppet.err "We're sorry, we could not parse that as a Semantic Version."
retry
end
puts
puts "Who wrote this module? [#{metadata.author}]"
metadata.update 'author' => user_input(metadata.author)
puts
puts "What license does this module code fall under? [#{metadata.license}]"
metadata.update 'license' => user_input(metadata.license)
puts
puts "How would you describe this module in a single sentence?"
metadata.update 'summary' => user_input(metadata.summary)
puts
puts "Where is this module's source code repository?"
metadata.update 'source' => user_input(metadata.source)
puts
puts "Where can others go to learn more about this module?#{ metadata.project_page && " [#{metadata.project_page}]" }"
metadata.update 'project_page' => user_input(metadata.project_page)
puts
puts "Where can others go to file issues about this module?#{ metadata.issues_url && " [#{metadata.issues_url}]" }"
metadata.update 'issues_url' => user_input(metadata.issues_url)
puts
puts '-' * 40
puts metadata.to_json
puts '-' * 40
puts
puts "About to generate this metadata; continue? [n/Y]"
if user_input('Y') !~ /^y(es)?$/i
puts "Aborting..."
exit 0
end
end
def user_input(default=nil)
print '--> '
input = STDIN.gets.chomp.strip
input = default if input == ''
return input
end
def destination(metadata)
return @dest if defined? @dest
@dest = Pathname.pwd + metadata.dashed_name
raise ArgumentError, "#{@dest} already exists." if @dest.exist?
return @dest
end
def duplicate_skeleton(metadata)
dest = destination(metadata)
puts
Puppet.notice "Generating module at #{dest}..."
FileUtils.cp_r skeleton_path, dest
populate_templates(metadata, dest)
return dest
end
def populate_templates(metadata, destination)
Puppet.notice "Populating templates..."
formatters = {
:erb => proc { |data, ctx| ERB.new(data).result(ctx) },
:template => proc { |data, _| data },
}
formatters.each do |type, block|
templates = destination + "**/*.#{type}"
Dir.glob(templates.to_s, File::FNM_DOTMATCH).each do |erb|
path = Pathname.new(erb)
content = block[path.read, binding]
target = path.parent + path.basename(".#{type}")
target.open('w') { |f| f.write(content) }
path.unlink
end
end
end
def skeleton_path
return @path if defined? @path
path = Pathname(Puppet.settings[:module_skeleton_dir])
path = Pathname(__FILE__).dirname + '../../module_tool/skeleton/templates/generator' unless path.directory?
@path = path
end
end