#
# Copyright (c) 2009-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This file is part of Ronin Gen.
#
# Ronin Gen is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin Gen is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ronin Gen. If not, see .
#
require 'ronin/gen/config'
require 'ronin/support/inflector'
require 'ronin/templates/erb'
require 'ronin/ui/output'
require 'parameters'
require 'data_paths/finders'
require 'fileutils'
module Ronin
module Gen
#
# The {Generator} class is a generate base-class for all file,
# source-code or directory generators.
#
# # Extending
#
# To create a new type of generator one can extend {Generator},
# {FileGenerator} or {DirGenerator} classes. The new generator can
# define it's own `class_options`, which are made available to other
# classes that extend our generator. The functionality of the generator
# is defined via instance methods, which are called sequentially when
# the generator is invoked.
#
# require 'ronin/gen/file_generator'
#
# module Ronin
# module Gen
# module Generators
# class MyGenerator < FileGenerator
#
# # generator options
# parameter :stuff, :type => true
# parameter :syntax, :type => String
# parameter :includes, :type => Array
#
# #
# # Performs the generation.
# #
# def generate
# erb 'some_template.erb', path
# end
#
# end
# end
# end
# end
#
# # Invoking
#
# To invoke the generator from ruby, one can call the {generate}
# class method with the options and arguments to run the generator with:
#
# MyGenerator.generate(
# :stuff => true,
# :syntax => 'bla',
# :includes => ['other']
# :path => 'path/to/file',
# )
#
# To make your generator accessible to the `ronin-gen` command, simply
# place your generator file within the `ronin/gen/generators` directory
# of any Ronin library. If your generator class is named
# `MyGenerator`, than it's ruby file must be named `my_generator.rb`.
#
# To run the generator using the `ronin-gen` command, simply specify
# it's underscored name:
#
# ronin-gen my_generator path/to/file --stuff \
# --syntax bla \
# --includes other
#
class Generator
include Parameters
include DataPaths::Finders
include FileUtils
include Templates::Erb
#
# Initializes the generator.
#
# @param [String] path
# The destination path for the generator.
#
# @param [Hash{Symbol => Object}] options
# The options for the generator.
#
# @yield [generator]
# The given block will be passed the newly created generator.
#
# @yieldparam [Generator]
# The newly created generator.
#
# @api semipublic
#
def initialize(options={})
initialize_params(options)
yield self if block_given?
end
#
# The name of the generator.
#
# @return [String]
# The generator name.
#
# @since 1.1.0
#
# @api semipublic
#
def self.generator_name
class_name = self.name.sub('Ronin::Gen::Generators::','')
return Support::Inflector.underscore(class_name.gsub('::',':'))
end
#
# The default data directory of the generator.
#
# @param [String] new_dir
# The new data directory.
#
# @return [String, nil]
# The data directory that the generator will search for source files
# within.
#
# @since 1.1.0
#
# @api semipublic
#
def self.data_dir(new_dir=nil)
if new_dir
@data_dir = new_dir
else
@data_dir ||= if superclass < Generator
superclass.data_dir
end
end
end
#
# Invokes the generator.
#
# @param [Hash] options
# Class options to use with the generator.
#
# @param [Array] arguments
# Additional arguments for the generator.
#
# @yield [generator]
# The given block will be passed the new generator.
#
# @yieldparam [Generator] generator
# The newly created generator object.
#
# @return [Generator]
# The generate object.
#
# @example
# gen.generate
#
# @since 0.2.0
#
# @api public
#
def self.generate(options={},&block)
generator = new(options,&block)
generator.generate!
return generator
end
#
# Sets up the generator and calls {#generate}.
#
# @see #setup
# @see #generate
#
# @since 1.1.0
#
# @api public
#
def generate!
setup
generate
end
#
# Default method to initialize any instance variables before any of
# the tasks are invoked.
#
# @since 1.0.0
#
# @api semipublic
#
def setup
end
#
# Default generator method.
#
# @since 0.2.0
#
# @api semipublic
#
def generate
end
protected
#
# Runs a command.
#
# @param [String] command
# The command or program to run.
#
# @param [Array] arguments
# Additional arguments to run the program with.
#
# @since 1.1.0
#
# @api semipublic
#
def run(command,*arguments)
print_action command, *arguments
system(command,*arguments)
end
#
# Changes the permissions of a files or directories.
#
# @param [String, Integer] mode
# The new permissions for the files or directories.
#
# @param [Array] paths
# The path to the files or directories.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#chmod-instance_method
#
def chmod(mode,paths)
print_action 'chmod', mode.to_s(8), *paths
super(mode,paths)
end
#
# Changes the permissions of files/directories, recursively.
#
# @param [String, Integer] mode
# The new permissions for the files or directories.
#
# @param [Array] paths
# The path to the files or directories.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#chmod_R-instance_method
#
def chmod_R(mode,paths)
print_action 'chmod -R', mode.to_s(8)
super(mode,paths)
end
#
# Changes ownership of files or directories.
#
# @param [String, nil] user
# The new owner of the files or directories.
#
# @param [String, nil] group
# The new group for the files or directories.
#
# @param [Array] paths
# The path to the files or directories.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#chown-instance_method
#
def chown(user,group,paths)
print_action 'chown', "#{user}:#{group}", *paths
super(user,group,paths)
end
#
# Changes ownership of files/directories, recursively.
#
# @param [String, nil] user
# The new owner of the files or directories.
#
# @param [String, nil] group
# The new group for the files or directories.
#
# @param [Array] paths
# The path to the files or directories.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#chown_R-instance_method
#
def chown_R(user,group,paths)
print_action 'chown -R', "#{user}:#{group}", *paths
super(user,group,paths)
end
#
# Copies a data file.
#
# @param [String] file
# The relative path to the data file.
#
# @param [String] destination
# The destination to copy the data file to.
#
# @since 0.2.0
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#cp-instance_method
#
def cp(file,destination=file)
print_action 'cp', destination
super(data_file(file),destination)
end
#
# Copies the contents of all data directories.
#
# @param [String] directory
# The data directories to copy from.
#
# @param [String, nil] destination
# The optional destination directory to copy the files to.
#
# @param [Hash] config
# The optional configuration information.
#
# @option config [Boolean] :recursive (false)
# Recursively copies the contents.
#
# @since 1.0.0
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#cp_r-instance_method
#
def cp_r(directory,destination=directory)
print_action 'cp -r', destination
data_dirs(directory) do |dir|
super(dir,destination)
end
end
#
# Installs a file.
#
# @param [String] src
# The file to install.
#
# @param [String] dest
# The destination path for the file.
#
# @param [Hash] options
# Additional options.
#
# @option options [String, Integer] :mode
# The permissions of the installed file.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#install-instance_method
#
def install(src,dest,options={})
options = {:mode => options[:mode]} # only pass in :mode
print_action 'install', src, dest
super(data_file(src),dest,options)
end
#
# Creates a hard link.
#
# @param [String] src
# The path file/directory for the hard link.
#
# @param [String] dest
# The destination file/directory of the hard link.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#ln-instance_method
#
def ln(src,dest)
print_action 'ln', src, dest
super(src,dest)
end
#
# Creates a symbolic link.
#
# @param [String] src
# The path file/directory for the symbolic link.
#
# @param [String] dest
# The destination file/directory of the symbolic link.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#ln_s-instance_method
#
def ln_s(src,dest)
print_action 'ln -s', src, dest
super(src,dest)
end
#
# Forcibly creates a symbolic link.
#
# @param [String] src
# The path file/directory for the symbolic link.
#
# @param [String] dest
# The destination file/directory of the symbolic link.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#ln_sf-instance_method
#
def ln_sf(src,dest)
print_action 'ln -sf', src, dest
super(src,dest)
end
#
# Creates an empty directory.
#
# @param [String] dir
# The relative path of the directory to create.
#
# @example
# mkdir 'sub/dir'
#
# @since 0.2.0
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#mkdir-instance_method
#
def mkdir(dir)
print_action 'mkdir', dir
super(dir)
end
#
# Creates an empty directory.
#
# @param [String] dir
# The relative path of the directory to create.
#
# @example
# mkdir 'sub/dir'
#
# @since 0.2.0
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#mkdir_p-instance_method
#
def mkdir_p(dir)
print_action 'mkdir -p', dir
super(dir)
end
#
# Moves a file or directory.
#
# @param [String] src
# The path to the file or directory.
#
# @param [String] dest
# The new path to move the file or directory to.
#
# @api semipublic
#
# @since 1.1.0
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#mv-instance_method
#
def mv(src,dest)
print_action 'mv', src, dest
super(src,dest)
end
#
# Removes one or more files.
#
# @param [Array] paths
# The paths of the files and directories to remove.
#
# @param [Hash] options
# Additional options.
#
# @option options [Boolean] :force
# Specifies whether to forcible remove the files.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#rm-instance_method
#
def rm(paths,options={})
options = {:force => options[:force]} # only pass in :force
print_action 'rm', *paths
super(paths,options)
end
#
# Recursively removes files and directories.
#
# @param [Array] paths
# The paths of the files and directories to remove.
#
# @param [Hash] options
# Additional options.
#
# @option options [Boolean] :force
# Specifies whether to forcible remove the files.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#rm_r-instance_method
#
def rm_r(paths,options={})
options = {:force => options[:force]} # only pass in :force
print_action 'rm -r', *paths
super(paths,options)
end
#
# Forcibly removes files and directories, recursively.
#
# @param [Array] paths
# The paths of the files and directories to remove.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#rm_rf-instance_method
#
def rm_rf(paths)
print_action 'rm -rf', *paths
super(paths)
end
#
# Removes one or more directories.
#
# @param [Array] dirs
# The paths of the directories.
#
# @since 1.1.0
#
# @api semipublic
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#rmdir-instance_method
#
def rmdir(dirs)
print_action 'rmdir', *dirs
super(dirs)
end
#
# Touches a file.
#
# @param [String] destination
# The relative path to the file to touch.
#
# @example
# touch 'TODO.txt'
#
# @since 0.2.0
#
# @see http://rubydoc.info/stdlib/fileutils/FileUtils#touch-instance_method
#
def touch(file)
print_action 'touch', file
return super(file)
end
#
# Opens a file for writing.
#
# @param [String] path
# The path of the file to write to.
#
# @yield [file]
# The given block will be passed the newly opened file.
#
# @yieldparam [File]
# The new file file, opened for writing.
#
# @since 1.1.0
#
# @api semipublic
#
def write(path,&block)
File.open(path,'wb',&block)
end
#
# Renders the ERB template and saves the result.
#
# @param [String] template_path
# The relative path to the template.
#
# @param [String, nil] destination
# The destination to write the result of the rendered template to.
#
# @return [nil, String]
# If destination is `nil`, the result of the rendered template
# will be returned.
#
# @example
# erb 'Rakefile.erb', 'Rakefile'
#
# @example
# erb '_helpers.erb'
#
# @since 0.2.0
#
def template(template_path,dest=nil)
template_path = data_path(template_path)
if dest
print_action 'erb', dest
File.open(dest,'w') do |file|
file.write(erb_file(template_path))
end
else
erb_file(template_path).chomp
end
end
private
#
# Joins the path with the Generators {data_dir}.
#
# @param [String] path
# A relative path.
#
# @return [String]
# The full `data/` directory path.
#
# @since 1.1.0
#
# @api private
#
def data_path(path)
if self.class.data_dir
path = File.join(self.class.data_dir,path)
end
return path
end
#
# Searches for a file within the Generators {data_dir}.
#
# @param [String] path
# The relative path to search for.
#
# @return [String]
# The path to the file.
#
# @raise [StandardError]
# The file could not be found in the Generators {data_dir}.
#
# @since 1.1.0
#
# @api private
#
def data_file(path)
unless (full_path = find_data_file(data_path(path)))
raise(StandardError,"cannot find generator file: #{path.dump}")
end
return full_path
end
#
# Searches for a directory within the Generators {data_dir}.
#
# @param [String] path
# The relative path to search for.
#
# @return [String]
# The path to the directory.
#
# @raise [StandardError]
# The directory could not be found in the Generators {data_dir}.
#
# @since 1.1.0
#
# @api private
#
def data_dir(path)
unless (full_path = find_data_dir(data_path(path)))
raise(StandardError,"cannot find generator directory: #{path.dump}")
end
return full_path
end
#
# Searches for all matching directories within the Generators {data_dir}.
#
# @param [String] path
# The relative directory path to search for.
#
# @yield [dir]
# The given block will be passed each found directory.
#
# @yieldparam [String] dir
# A directory with the same relative path.
#
# @since 1.1.0
#
# @api private
#
def data_dirs(path,&block)
each_data_dir(data_path(directory),&block)
end
# ANSI Bold code
BOLD = "\e[1m"
# ANSI Green code
GREEN = "\e[32m"
# ANSI Clear code
CLEAR = "\e[0m"
#
# Prints a file action.
#
# @param [String] command
# The command/options that represents the file action.
#
# @param [Array] arguments
# Additional arguments related to the file action.
#
# @since 1.1.0
#
# @api private
#
def print_action(command,*arguments)
unless UI::Output.silent?
arguments = arguments.join(' ')
if $stdout.tty?
command = BOLD + GREEN + command + CLEAR
end
puts "\t#{command}\t#{arguments}"
end
end
end
end
end