josevalim-thor 0.10.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/CHANGELOG.rdoc +73 -0
- data/LICENSE +20 -0
- data/README.markdown +76 -0
- data/Rakefile +6 -0
- data/bin/rake2thor +87 -0
- data/bin/thor +7 -0
- data/lib/thor.rb +229 -0
- data/lib/thor/actions.rb +147 -0
- data/lib/thor/actions/commands.rb +61 -0
- data/lib/thor/actions/copy_file.rb +32 -0
- data/lib/thor/actions/create_file.rb +48 -0
- data/lib/thor/actions/directory.rb +36 -0
- data/lib/thor/actions/empty_directory.rb +30 -0
- data/lib/thor/actions/get.rb +58 -0
- data/lib/thor/actions/gsub_file.rb +77 -0
- data/lib/thor/actions/inject_into_file.rb +93 -0
- data/lib/thor/actions/template.rb +37 -0
- data/lib/thor/actions/templater.rb +163 -0
- data/lib/thor/base.rb +447 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +59 -0
- data/lib/thor/core_ext/ordered_hash.rb +133 -0
- data/lib/thor/error.rb +27 -0
- data/lib/thor/group.rb +90 -0
- data/lib/thor/option.rb +210 -0
- data/lib/thor/options.rb +282 -0
- data/lib/thor/runner.rb +296 -0
- data/lib/thor/shell/basic.rb +198 -0
- data/lib/thor/task.rb +85 -0
- data/lib/thor/tasks.rb +3 -0
- data/lib/thor/tasks/install.rb +35 -0
- data/lib/thor/tasks/package.rb +31 -0
- data/lib/thor/tasks/spec.rb +70 -0
- data/lib/thor/util.rb +209 -0
- metadata +93 -0
data/lib/thor/actions.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action|
|
4
|
+
require action
|
5
|
+
end
|
6
|
+
|
7
|
+
class Thor
|
8
|
+
module Actions
|
9
|
+
attr_accessor :behavior
|
10
|
+
|
11
|
+
# On inclusion, add some options to base.
|
12
|
+
#
|
13
|
+
def self.included(base) #:nodoc:
|
14
|
+
return unless base.respond_to?(:class_option)
|
15
|
+
|
16
|
+
base.class_option :pretend, :type => :boolean, :aliases => "-p",
|
17
|
+
:desc => "Run but do not make any changes"
|
18
|
+
|
19
|
+
base.class_option :force, :type => :boolean, :aliases => "-f",
|
20
|
+
:desc => "Overwrite files that already exist"
|
21
|
+
|
22
|
+
base.class_option :skip, :type => :boolean, :aliases => "-s",
|
23
|
+
:desc => "Skip files that already exist"
|
24
|
+
|
25
|
+
base.class_option :quiet, :type => :boolean, :aliases => "-q",
|
26
|
+
:desc => "Supress status output"
|
27
|
+
|
28
|
+
# TODO Add git support
|
29
|
+
base.class_option :git, :type => :boolean, :aliases => "-g",
|
30
|
+
:desc => "Modify files with git (note: git must be on the path)"
|
31
|
+
|
32
|
+
# TODO Add chmod support
|
33
|
+
base.class_option :chmod, :type => :string, :aliases => "-c",
|
34
|
+
:desc => "Set the mode of added/copied files"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Extends initializer to add more configuration options.
|
38
|
+
#
|
39
|
+
# ==== Configuration
|
40
|
+
# behavior<Symbol>:: The actions default behavior. Can be :invoke or :revoke.
|
41
|
+
# It also accepts :force, :skip and :pretend to set the behavior
|
42
|
+
# and the respective option.
|
43
|
+
#
|
44
|
+
# root<String>:: The root directory needed for some actions. It's also known
|
45
|
+
# as destination root.
|
46
|
+
#
|
47
|
+
# in_root<Boolean>:: When true, creates the root directory if it does not exist
|
48
|
+
# and move to it. False by default.
|
49
|
+
#
|
50
|
+
def initialize(args=[], options={}, config={})
|
51
|
+
self.behavior = case config[:behavior]
|
52
|
+
when :force
|
53
|
+
options.merge!(:force => true, 'force' => true)
|
54
|
+
:invoke
|
55
|
+
when :skip
|
56
|
+
options.merge!(:skip => true, 'skip' => true)
|
57
|
+
:invoke
|
58
|
+
when :pretend
|
59
|
+
options.merge!(:pretend => true, 'pretend' => true)
|
60
|
+
:invoke
|
61
|
+
when :revoke
|
62
|
+
:revoke
|
63
|
+
else
|
64
|
+
:invoke
|
65
|
+
end
|
66
|
+
|
67
|
+
self.root = config[:root]
|
68
|
+
if config[:in_root]
|
69
|
+
FileUtils.mkdir_p(root) unless File.exist?(root)
|
70
|
+
FileUtils.cd(root)
|
71
|
+
end
|
72
|
+
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
# Wraps an action object and call it accordingly to the thor class behavior.
|
77
|
+
#
|
78
|
+
def action(instance)
|
79
|
+
if behavior == :revoke
|
80
|
+
instance.revoke!
|
81
|
+
else
|
82
|
+
instance.invoke!
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the root for this thor class (also aliased as destination root).
|
87
|
+
#
|
88
|
+
def root
|
89
|
+
@root_stack.last
|
90
|
+
end
|
91
|
+
alias :destination_root :root
|
92
|
+
|
93
|
+
# Sets the root for this thor class. Relatives path are added to the
|
94
|
+
# directory where the script was invoked and expanded.
|
95
|
+
#
|
96
|
+
def root=(root)
|
97
|
+
@root_stack ||= []
|
98
|
+
@root_stack[0] = File.expand_path(root || '')
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the given path relative to the absolute root (ie, root where
|
102
|
+
# the script started).
|
103
|
+
#
|
104
|
+
def relative_to_absolute_root(path, remove_dot=true)
|
105
|
+
path = path.gsub(@root_stack[0], '.')
|
106
|
+
remove_dot ? path[2..-1] : path
|
107
|
+
end
|
108
|
+
|
109
|
+
# Get the source root in the class. Raises an error if a source root is
|
110
|
+
# not specified in the thor class.
|
111
|
+
#
|
112
|
+
def source_root
|
113
|
+
self.class.source_root
|
114
|
+
rescue NoMethodError => e
|
115
|
+
raise NoMethodError, "You have to specify the class method source_root in your thor class."
|
116
|
+
end
|
117
|
+
|
118
|
+
# Do something in the root or on a provided subfolder. If a relative path
|
119
|
+
# is given it's referenced from the current root. The full path is yielded
|
120
|
+
# to the block you provide. The path is set back to the previous path when
|
121
|
+
# the method exits.
|
122
|
+
#
|
123
|
+
# ==== Parameters
|
124
|
+
# dir<String>:: the directory to move to.
|
125
|
+
#
|
126
|
+
def inside(dir='', &block)
|
127
|
+
@root_stack.push File.expand_path(dir, root)
|
128
|
+
FileUtils.mkdir_p(root) unless File.exist?(root)
|
129
|
+
FileUtils.cd(root) { block.arity == 1 ? yield(root) : yield }
|
130
|
+
@root_stack.pop
|
131
|
+
end
|
132
|
+
|
133
|
+
# Goes to the root and execute the given block.
|
134
|
+
#
|
135
|
+
def in_root
|
136
|
+
inside(@root_stack.first) { yield }
|
137
|
+
end
|
138
|
+
|
139
|
+
protected
|
140
|
+
|
141
|
+
def say_status_if_log(status, message, log_status)
|
142
|
+
color = log_status.is_a?(Symbol) ? log_status : :green
|
143
|
+
shell.say_status status, message, color if log_status
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Thor
|
2
|
+
module Actions
|
3
|
+
# Executes a command.
|
4
|
+
#
|
5
|
+
# ==== Parameters
|
6
|
+
# command<String>:: the command to be executed.
|
7
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
8
|
+
# If a symbol is given, uses it as the output color.
|
9
|
+
#
|
10
|
+
# ==== Example
|
11
|
+
#
|
12
|
+
# inside('vendor') do
|
13
|
+
# run('ln -s ~/edge rails')
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
def run(command, log_status=true)
|
17
|
+
say_status_if_log :run, "#{command} from #{relative_to_absolute_root(root, false)}", log_status
|
18
|
+
`#{command}` unless options[:pretend]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Executes a ruby script (taking into account WIN32 platform quirks).
|
22
|
+
#
|
23
|
+
# ==== Parameters
|
24
|
+
# command<String>:: the command to be executed.
|
25
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
26
|
+
# If a symbol is given, uses it as the output color.
|
27
|
+
#
|
28
|
+
def run_ruby_script(command, log_status=true)
|
29
|
+
run("ruby #{command}", log_status)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Run a thor command. A hash of options can be given and it's converted to
|
33
|
+
# switches.
|
34
|
+
#
|
35
|
+
# ==== Parameters
|
36
|
+
# task<String>:: the task to be invoked
|
37
|
+
# args<Array>:: arguments to the task
|
38
|
+
# options<Hash>:: a hash with options used on invocation
|
39
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
40
|
+
# If a symbol is given, uses it as the output color.
|
41
|
+
#
|
42
|
+
# ==== Examples
|
43
|
+
#
|
44
|
+
# thor :install, "http://gist.github.com/103208"
|
45
|
+
# #=> thor install http://gist.github.com/103208
|
46
|
+
#
|
47
|
+
# thor :list, :all => true, :substring => 'rails'
|
48
|
+
# #=> thor list --all --substring=rails
|
49
|
+
#
|
50
|
+
def thor(task, *args)
|
51
|
+
log_status = args.last.is_a?(Symbol) || [true, false].include?(args.last) ? args.pop : true
|
52
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
53
|
+
|
54
|
+
in_root do
|
55
|
+
args.unshift "thor #{task}"
|
56
|
+
args.push Thor::Options.to_switches(options)
|
57
|
+
run args.join(' ').strip, log_status
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'thor/actions/templater'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
# Copies the file from the relative source to the relative destination. If
|
7
|
+
# the destination is not given it's assumed to be equal to the source.
|
8
|
+
#
|
9
|
+
# ==== Parameters
|
10
|
+
# source<String>:: the relative path to the source root
|
11
|
+
# destination<String>:: the relative path to the destination root
|
12
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
13
|
+
#
|
14
|
+
# ==== Examples
|
15
|
+
#
|
16
|
+
# copy_file "README", "doc/README"
|
17
|
+
#
|
18
|
+
# copy_file "doc/README"
|
19
|
+
#
|
20
|
+
def copy_file(source, destination=nil, log_status=true)
|
21
|
+
action CopyFile.new(self, source, destination || source, log_status)
|
22
|
+
end
|
23
|
+
|
24
|
+
class CopyFile < Templater #:nodoc:
|
25
|
+
|
26
|
+
def render
|
27
|
+
@render ||= ::File.read(source)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'thor/actions/templater'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
# Create a new file relative to the destination root with the given data,
|
7
|
+
# which is the return value of a block or a data string.
|
8
|
+
#
|
9
|
+
# ==== Parameters
|
10
|
+
# destination<String>:: the relative path to the destination root.
|
11
|
+
# data<String|NilClass>:: the data to append to the file.
|
12
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
13
|
+
#
|
14
|
+
# ==== Examples
|
15
|
+
#
|
16
|
+
# create_file "lib/fun_party.rb" do
|
17
|
+
# hostname = ask("What is the virtual hostname I should use?")
|
18
|
+
# "vhost.name = #{hostname}"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# create_file "config/apach.conf", "your apache config"
|
22
|
+
#
|
23
|
+
def create_file(destination, data=nil, log_status=true, &block)
|
24
|
+
action AddFile.new(self, destination, block || data.to_s, log_status)
|
25
|
+
end
|
26
|
+
|
27
|
+
# AddFile is a subset of Template, which instead of rendering a file with
|
28
|
+
# ERB, it gets the content from the user.
|
29
|
+
#
|
30
|
+
class CreateFile < Templater #:nodoc:
|
31
|
+
attr_reader :data
|
32
|
+
|
33
|
+
def initialize(base, destination, data, log_status)
|
34
|
+
super(base, nil, destination, log_status)
|
35
|
+
@data = data
|
36
|
+
end
|
37
|
+
|
38
|
+
def render
|
39
|
+
@render ||= if data.is_a?(Proc)
|
40
|
+
data.call
|
41
|
+
else
|
42
|
+
data
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'thor/actions/templater'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
# Copies interactively the files from source directory to root directory.
|
7
|
+
# Use it just to copy static files.
|
8
|
+
#
|
9
|
+
# ==== Parameters
|
10
|
+
# source<String>:: the relative path to the source root
|
11
|
+
# destination<String>:: the relative path to the destination root
|
12
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
13
|
+
#
|
14
|
+
# ==== Examples
|
15
|
+
#
|
16
|
+
# directory "doc"
|
17
|
+
#
|
18
|
+
def directory(source, destination=nil, log_status=true)
|
19
|
+
action Directory.new(self, source, destination || source, log_status)
|
20
|
+
end
|
21
|
+
|
22
|
+
class Directory < Templater #:nodoc:
|
23
|
+
|
24
|
+
def invoke!
|
25
|
+
files = Dir[File.join(source, '**', '*')].select{ |f| !File.directory?(f) }
|
26
|
+
|
27
|
+
files.each do |file_source|
|
28
|
+
file_destination = File.join(relative_destination, file_source.gsub(source, ''))
|
29
|
+
file_source.gsub!(base.source_root, '.')
|
30
|
+
base.copy_file(file_source, file_destination, @log_status)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'thor/actions/templater'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
# Creates an empty directory.
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# destination<String>:: the relative path to the destination root
|
10
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
11
|
+
#
|
12
|
+
# ==== Examples
|
13
|
+
#
|
14
|
+
# empty_directory "doc"
|
15
|
+
#
|
16
|
+
def empty_directory(destination, log_status=true)
|
17
|
+
action EmptyDirectory.new(self, nil, destination, log_status)
|
18
|
+
end
|
19
|
+
|
20
|
+
class EmptyDirectory < Templater #:nodoc:
|
21
|
+
|
22
|
+
def invoke!
|
23
|
+
invoke_with_options!(base.options) do
|
24
|
+
::FileUtils.mkdir_p(destination)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'thor/actions/templater'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
class Thor
|
5
|
+
module Actions
|
6
|
+
|
7
|
+
# Gets the content at the given address and places it at the given relative
|
8
|
+
# destination. If a block is given instead of destination, the content of
|
9
|
+
# the url is yielded and used as location.
|
10
|
+
#
|
11
|
+
# ==== Parameters
|
12
|
+
# source<String>:: the address of the given content
|
13
|
+
# destination<String>:: the relative path to the destination root
|
14
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
15
|
+
#
|
16
|
+
# ==== Examples
|
17
|
+
#
|
18
|
+
# get "http://gist.github.com/103208", "doc/README"
|
19
|
+
#
|
20
|
+
# get "http://gist.github.com/103208" do |content|
|
21
|
+
# content.split("\n").first
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
def get(source, destination=nil, log_status=true, &block)
|
25
|
+
action Get.new(self, source, block || destination, log_status)
|
26
|
+
end
|
27
|
+
|
28
|
+
class Get < Templater #:nodoc:
|
29
|
+
|
30
|
+
def render
|
31
|
+
@render ||= open(source).read
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def source=(source)
|
37
|
+
if source =~ /^http\:\/\//
|
38
|
+
@source = source
|
39
|
+
else
|
40
|
+
super(source)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def destination=(destination)
|
45
|
+
destination = if destination.nil?
|
46
|
+
File.basename(source)
|
47
|
+
elsif destination.is_a?(Proc)
|
48
|
+
destination.arity == 1 ? destination.call(render) : destination.call
|
49
|
+
else
|
50
|
+
destination
|
51
|
+
end
|
52
|
+
|
53
|
+
super(destination)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class Thor
|
2
|
+
module Actions
|
3
|
+
|
4
|
+
# Run a regular expression replacement on a file.
|
5
|
+
#
|
6
|
+
# ==== Parameters
|
7
|
+
# path<String>:: path of the file to be changed
|
8
|
+
# flag<Regexp|String>:: the regexp or string to be replaced
|
9
|
+
# replacement<String>:: the replacement, can be also given as a block
|
10
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
11
|
+
# If a symbol is given, uses it as the output color.
|
12
|
+
#
|
13
|
+
# ==== Example
|
14
|
+
#
|
15
|
+
# gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
|
16
|
+
#
|
17
|
+
# gsub_file 'README', /rake/, :green do |match|
|
18
|
+
# match << " no more. Use thor!"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
def gsub_file(path, flag, *args, &block)
|
22
|
+
log_status = args.last.is_a?(Symbol) || [ true, false ].include?(args.last) ? args.pop : true
|
23
|
+
|
24
|
+
path = File.expand_path(path, root)
|
25
|
+
say_status_if_log :gsub, relative_to_absolute_root(path), log_status
|
26
|
+
|
27
|
+
unless options[:pretend]
|
28
|
+
content = File.read(path)
|
29
|
+
content.gsub!(flag, *args, &block)
|
30
|
+
File.open(path, 'wb') { |file| file.write(content) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Append text to a file.
|
35
|
+
#
|
36
|
+
# ==== Parameters
|
37
|
+
# path<String>:: path of the file to be changed
|
38
|
+
# data<String>:: the data to append to the file, can be also given as a block.
|
39
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
40
|
+
# If a symbol is given, uses it as the output color.
|
41
|
+
#
|
42
|
+
# ==== Example
|
43
|
+
#
|
44
|
+
# append_file 'config/environments/test.rb', 'config.gem "rspec"'
|
45
|
+
#
|
46
|
+
def append_file(path, data=nil, log_status=true, &block)
|
47
|
+
path = File.expand_path(path, root)
|
48
|
+
say_status_if_log :append, relative_to_absolute_root(path), log_status
|
49
|
+
|
50
|
+
File.open(path, 'ab') { |file| file.write(data || block.call) } unless options[:pretend]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Prepend text to a file.
|
54
|
+
#
|
55
|
+
# ==== Parameters
|
56
|
+
# path<String>:: path of the file to be changed
|
57
|
+
# data<String>:: the data to prepend to the file, can be also given as a block.
|
58
|
+
# log_status<Boolean>:: if false, does not log the status. True by default.
|
59
|
+
# If a symbol is given, uses it as the output color.
|
60
|
+
#
|
61
|
+
# ==== Example
|
62
|
+
#
|
63
|
+
# prepend_file 'config/environments/test.rb', 'config.gem "rspec"'
|
64
|
+
#
|
65
|
+
def prepend_file(path, data=nil, log_status=true, &block)
|
66
|
+
path = File.expand_path(path, root)
|
67
|
+
say_status_if_log :prepend, relative_to_absolute_root(path), log_status
|
68
|
+
|
69
|
+
unless options[:pretend]
|
70
|
+
content = data || block.call
|
71
|
+
content << File.read(path)
|
72
|
+
File.open(path, 'wb') { |file| file.write(content) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|