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.
@@ -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