wycats-thor 0.11.4 → 0.11.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG.rdoc +4 -2
  2. data/Thorfile +57 -0
  3. data/VERSION +1 -0
  4. data/lib/thor.rb +13 -6
  5. data/lib/thor/base.rb +4 -1
  6. data/lib/thor/invocation.rb +1 -1
  7. data/lib/thor/rake_compat.rb +67 -0
  8. data/lib/thor/runner.rb +12 -8
  9. data/lib/thor/shell/basic.rb +8 -4
  10. data/lib/thor/task.rb +9 -1
  11. data/lib/thor/util.rb +28 -8
  12. data/spec/actions/create_file_spec.rb +170 -0
  13. data/spec/actions/directory_spec.rb +118 -0
  14. data/spec/actions/empty_directory_spec.rb +91 -0
  15. data/spec/actions/file_manipulation_spec.rb +242 -0
  16. data/spec/actions/inject_into_file_spec.rb +80 -0
  17. data/spec/actions_spec.rb +291 -0
  18. data/spec/base_spec.rb +235 -0
  19. data/spec/core_ext/hash_with_indifferent_access_spec.rb +43 -0
  20. data/spec/core_ext/ordered_hash_spec.rb +115 -0
  21. data/spec/fixtures/bundle/execute.rb +6 -0
  22. data/spec/fixtures/doc/config.rb +1 -0
  23. data/spec/group_spec.rb +177 -0
  24. data/spec/invocation_spec.rb +107 -0
  25. data/spec/parser/argument_spec.rb +47 -0
  26. data/spec/parser/arguments_spec.rb +64 -0
  27. data/spec/parser/option_spec.rb +212 -0
  28. data/spec/parser/options_spec.rb +255 -0
  29. data/spec/rake_compat_spec.rb +64 -0
  30. data/spec/runner_spec.rb +204 -0
  31. data/spec/shell/basic_spec.rb +206 -0
  32. data/spec/shell/color_spec.rb +41 -0
  33. data/spec/shell_spec.rb +25 -0
  34. data/spec/spec_helper.rb +52 -0
  35. data/spec/task_spec.rb +82 -0
  36. data/spec/thor_spec.rb +234 -0
  37. data/spec/util_spec.rb +192 -0
  38. metadata +56 -36
  39. data/Rakefile +0 -6
  40. data/lib/thor/tasks.rb +0 -4
  41. data/lib/thor/tasks/install.rb +0 -35
  42. data/lib/thor/tasks/package.rb +0 -31
  43. data/lib/thor/tasks/spec.rb +0 -70
@@ -1,9 +1,11 @@
1
1
  == TODO
2
2
 
3
3
  * Improve spec coverage for Thor::Runner
4
- * Improve help output to list shorthand switches, too
5
4
 
6
- == Current
5
+ == 0.11.x, released 2009-07-01
6
+
7
+ * Added a rake compatibility layer. It allows you to use spec and rdoc tasks on
8
+ Thor classes.
7
9
 
8
10
  * BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore
9
11
  since it wrong behavior to the invocation system.
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'thor/rake_compat'
3
+ require 'spec/rake/spectask'
4
+ require 'rdoc/task'
5
+
6
+ GEM_NAME = 'thor'
7
+ EXTRA_RDOC_FILES = ["README.rdoc", "LICENSE", "CHANGELOG.rdoc", "VERSION", "Thorfile"]
8
+
9
+ class Default < Thor
10
+ include Thor::RakeCompat
11
+
12
+ Spec::Rake::SpecTask.new(:spec) do |t|
13
+ t.spec_opts = ['--options', "spec/spec.opts"]
14
+ t.spec_files = FileList['spec/**/*_spec.rb']
15
+ end
16
+
17
+ Spec::Rake::SpecTask.new(:rcov) do |t|
18
+ t.spec_opts = ['--options', "spec/spec.opts"]
19
+ t.spec_files = FileList['spec/**/*_spec.rb']
20
+ t.rcov = true
21
+ t.rcov_dir = "rcov"
22
+ end
23
+
24
+ RDoc::Task.new do |rdoc|
25
+ rdoc.main = "README.rdoc"
26
+ rdoc.rdoc_dir = "rdoc"
27
+ rdoc.title = GEM_NAME
28
+ rdoc.rdoc_files.include(*EXTRA_RDOC_FILES)
29
+ rdoc.rdoc_files.include('lib/**/*.rb')
30
+ rdoc.options << '--line-numbers' << '--inline-source'
31
+ end
32
+
33
+ begin
34
+ require 'jeweler'
35
+ Jeweler::Tasks.new do |s|
36
+ s.name = GEM_NAME
37
+ s.version = "0.11.4"
38
+ s.rubyforge_project = "thor"
39
+ s.platform = Gem::Platform::RUBY
40
+ s.summary = "A scripting framework that replaces rake, sake and rubigen"
41
+ s.email = "ruby-thor@googlegroups.com"
42
+ s.homepage = "http://yehudakatz.com"
43
+ s.description = "A scripting framework that replaces rake, sake and rubigen"
44
+ s.authors = ['Yehuda Katz', 'José Valim']
45
+ s.has_rdoc = true
46
+ s.extra_rdoc_files = EXTRA_RDOC_FILES
47
+ s.require_path = 'lib'
48
+ s.bindir = "bin"
49
+ s.executables = %w( thor rake2thor )
50
+ s.files = s.extra_rdoc_files + Dir.glob("{bin,lib}/**/*")
51
+ s.files.exclude 'spec/sandbox/**/*'
52
+ s.test_files.exclude 'spec/sandbox/**/*'
53
+ end
54
+ rescue LoadError
55
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
56
+ end
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.11.5
@@ -165,18 +165,25 @@ class Thor
165
165
  shell.say task.description
166
166
  else
167
167
  list = (options[:short] ? tasks : all_tasks).map do |_, task|
168
- item = [ banner(task, options[:namespace]), "\n" ]
169
- item.last << "# #{task.short_description}\n" if task.short_description
170
- item
168
+ item = [ banner(task, options[:namespace]) ]
169
+ item << "# #{task.short_description}" if task.short_description
170
+ item << " "
171
171
  end
172
172
 
173
+ options[:ident] ||= 2
173
174
  if options[:short]
174
- shell.print_table(list)
175
+ shell.print_list(list, :ident => options[:ident])
175
176
  else
176
177
  shell.say "Tasks:"
177
- shell.print_table(list)
178
- class_options_help(shell, "Class")
178
+ shell.print_list(list, :ident => options[:ident])
179
179
  end
180
+
181
+ Thor::Util.thor_classes_in(self).each do |subclass|
182
+ namespace = options[:namespace] == true || subclass.namespace.gsub(/^#{self.namespace}:/, '')
183
+ subclass.help(shell, options.merge(:short => true, :namespace => namespace))
184
+ end
185
+
186
+ class_options_help(shell, "Class") unless options[:short]
180
187
  end
181
188
  end
182
189
 
@@ -8,7 +8,10 @@ require 'thor/task'
8
8
  require 'thor/util'
9
9
 
10
10
  class Thor
11
+ # Shortcuts for help.
11
12
  HELP_MAPPINGS = %w(-h -? --help -D)
13
+
14
+ # Thor methods that should not be overwritten by the user.
12
15
  THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
13
16
  action add_file create_file in_root inside run run_ruby_script)
14
17
 
@@ -335,7 +338,7 @@ class Thor
335
338
  def namespace(name=nil)
336
339
  case name
337
340
  when nil
338
- @namespace ||= Thor::Util.constant_to_namespace(self, false)
341
+ @namespace ||= Thor::Util.namespace_from_thor_class(self, false)
339
342
  else
340
343
  @namespace = name.to_s
341
344
  end
@@ -11,7 +11,7 @@ class Thor
11
11
  def prepare_for_invocation(key, name) #:nodoc:
12
12
  case name
13
13
  when Symbol, String
14
- Thor::Util.namespace_to_thor_class(name.to_s, false)
14
+ Thor::Util.namespace_to_thor_class_and_task(name.to_s, false)
15
15
  else
16
16
  name
17
17
  end
@@ -0,0 +1,67 @@
1
+ require 'rake'
2
+
3
+ class Thor
4
+ # Adds a compatibility layer to your Thor classes which allows you to use
5
+ # rake package tasks. For example, to use rspec rake tasks, one can do:
6
+ #
7
+ # require 'thor/rake_compat'
8
+ #
9
+ # class Default < Thor
10
+ # include Thor::RakeCompat
11
+ #
12
+ # Spec::Rake::SpecTask.new(:spec) do |t|
13
+ # t.spec_opts = ['--options', "spec/spec.opts"]
14
+ # t.spec_files = FileList['spec/**/*_spec.rb']
15
+ # end
16
+ # end
17
+ #
18
+ module RakeCompat
19
+ def self.rake_classes
20
+ @rake_classes ||= []
21
+ end
22
+
23
+ def self.included(base)
24
+ # Hack. Make rakefile point to invoker, so rdoc task is generated properly.
25
+ Rake.application.instance_variable_set(:@rakefile, caller[0].match(/(.*):\d+/)[1])
26
+ self.rake_classes << base
27
+ end
28
+ end
29
+ end
30
+
31
+ class Object #:nodoc:
32
+ alias :rake_task :task
33
+ alias :rake_namespace :namespace
34
+
35
+ def task(*args, &block)
36
+ task = rake_task(*args, &block)
37
+
38
+ if klass = Thor::RakeCompat.rake_classes.last
39
+ non_namespaced_name = task.name.split(':').last
40
+
41
+ description = non_namespaced_name
42
+ description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ')
43
+ description.strip!
44
+
45
+ klass.desc description, task.comment || non_namespaced_name
46
+ klass.class_eval <<-METHOD
47
+ def #{non_namespaced_name}(#{task.arg_names.join(', ')})
48
+ Rake::Task[#{task.name.to_sym.inspect}].invoke(#{task.arg_names.join(', ')})
49
+ end
50
+ METHOD
51
+ end
52
+
53
+ task
54
+ end
55
+
56
+ def namespace(name, &block)
57
+ if klass = Thor::RakeCompat.rake_classes.last
58
+ const_name = Thor::Util.camel_case(name.to_s).to_sym
59
+ klass.const_set(const_name, Class.new(Thor))
60
+ new_klass = klass.const_get(const_name)
61
+ Thor::RakeCompat.rake_classes << new_klass
62
+ end
63
+
64
+ rake_namespace(name, &block)
65
+ Thor::RakeCompat.rake_classes.pop
66
+ end
67
+ end
@@ -12,8 +12,9 @@ class Thor::Runner < Thor #:nodoc:
12
12
  def help(meth=nil)
13
13
  if meth && !self.respond_to?(meth)
14
14
  initialize_thorfiles(meth)
15
- klass, task = Thor::Util.namespace_to_thor_class(meth)
16
- klass.start(["-h", task].compact, :shell => self.shell) # send mapping -h because it works with Thor::Group too
15
+ klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
16
+ # Send mapping -h because it works with Thor::Group too
17
+ klass.start(["-h", task].compact, :shell => self.shell)
17
18
  else
18
19
  super
19
20
  end
@@ -25,12 +26,12 @@ class Thor::Runner < Thor #:nodoc:
25
26
  def method_missing(meth, *args)
26
27
  meth = meth.to_s
27
28
  initialize_thorfiles(meth)
28
- klass, task = Thor::Util.namespace_to_thor_class(meth)
29
+ klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
29
30
  args.unshift(task) if task
30
31
  klass.start(args, :shell => shell)
31
32
  end
32
33
 
33
- desc "install NAME", "Install a Thor file into your system tasks, optionally named for future updates"
34
+ desc "install NAME", "Install an optionally named Thor file into your system tasks"
34
35
  method_options :as => :string, :relative => :boolean
35
36
  def install(name)
36
37
  initialize_thorfiles
@@ -76,7 +77,7 @@ class Thor::Runner < Thor #:nodoc:
76
77
  thor_yaml[as] = {
77
78
  :filename => Digest::MD5.hexdigest(name + as),
78
79
  :location => location,
79
- :namespaces => Thor::Util.namespaces_in_contents(contents, base)
80
+ :namespaces => Thor::Util.namespaces_in_content(contents, base)
80
81
  }
81
82
 
82
83
  save_yaml(thor_yaml)
@@ -130,8 +131,7 @@ class Thor::Runner < Thor #:nodoc:
130
131
  display_klasses(true, klasses)
131
132
  end
132
133
 
133
- desc "list [SEARCH]",
134
- "List the available thor tasks (--substring means SEARCH anywhere in the namespace)"
134
+ desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
135
135
  method_options :substring => :boolean, :group => :string, :all => :boolean
136
136
  def list(search="")
137
137
  initialize_thorfiles
@@ -269,6 +269,10 @@ class Thor::Runner < Thor #:nodoc:
269
269
  end
270
270
 
271
271
  unless klasses.empty?
272
+ klasses.dup.each do |klass|
273
+ klasses -= Thor::Util.thor_classes_in(klass)
274
+ end
275
+
272
276
  klasses.each { |k| display_tasks(k) }
273
277
  else
274
278
  say "\033[1;34mNo Thor tasks available\033[0m"
@@ -285,7 +289,7 @@ class Thor::Runner < Thor #:nodoc:
285
289
  say shell.set_color(base, color, true)
286
290
  say "-" * base.length
287
291
 
288
- klass.help(shell, :short => true, :namespace => true)
292
+ klass.help(shell, :short => true, :ident => 0, :namespace => true)
289
293
  end
290
294
  end
291
295
  end
@@ -80,17 +80,21 @@ class Thor
80
80
  #
81
81
  # ==== Parameters
82
82
  # list<Array[String, String, ...]>
83
- # mode<Symbol>:: Can be :rows or :inline. Defaults to :rows.
84
83
  #
85
- def print_list(list, mode=:rows)
84
+ # ==== Options
85
+ # mode:: Can be :rows or :inline. Defaults to :rows.
86
+ # ident:: Ident each item with the value given.
87
+ #
88
+ def print_list(list, options={})
86
89
  return if list.empty?
87
90
 
88
- content = case mode
91
+ ident = " " * (options[:ident] || 0)
92
+ content = case options[:mode]
89
93
  when :inline
90
94
  last = list.pop
91
95
  "#{list.join(", ")}, and #{last}"
92
96
  else # rows
93
- list.join("\n")
97
+ ident + list.join("\n#{ident}")
94
98
  end
95
99
 
96
100
  $stdout.puts content
@@ -38,7 +38,15 @@ class Thor
38
38
  #
39
39
  def formatted_usage(klass=nil, namespace=false, show_options=true)
40
40
  formatted = ''
41
- formatted << "#{klass.namespace.gsub(/^default/,'')}:" if klass && namespace
41
+
42
+ formatted = if namespace.is_a?(String)
43
+ "#{namespace}:"
44
+ elsif klass && namespace
45
+ "#{klass.namespace.gsub(/^default/,'')}:"
46
+ else
47
+ ""
48
+ end
49
+
42
50
  formatted << formatted_arguments(klass)
43
51
  formatted << " #{formatted_options}" if show_options
44
52
  formatted.strip!
@@ -8,8 +8,7 @@ class Thor
8
8
  #
9
9
  # 1) Methods to convert thor namespaces to constants and vice-versa.
10
10
  #
11
- # Thor::Utils.constant_to_namespace(Foo::Bar::Baz) #=> "foo:bar:baz"
12
- # Thor::Utils.namespace_to_constant("foo:bar:baz") #=> Foo::Bar::Baz
11
+ # Thor::Utils.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz"
13
12
  #
14
13
  # 2) Loading thor files and sandboxing:
15
14
  #
@@ -44,15 +43,15 @@ class Thor
44
43
  # ==== Returns
45
44
  # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
46
45
  #
47
- def self.constant_to_namespace(constant, remove_default=true)
46
+ def self.namespace_from_thor_class(constant, remove_default=true)
48
47
  constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
49
48
  constant = snake_case(constant).squeeze(":")
50
49
  constant.gsub!(/^default/, '') if remove_default
51
50
  constant
52
51
  end
53
52
 
54
- # Given the contents, evaluate it inside the sandbox and returns the thor
55
- # classes defined in the sandbox.
53
+ # Given the contents, evaluate it inside the sandbox and returns the
54
+ # namespaces defined in the sandbox.
56
55
  #
57
56
  # ==== Parameters
58
57
  # contents<String>
@@ -60,7 +59,7 @@ class Thor
60
59
  # ==== Returns
61
60
  # Array[Object]
62
61
  #
63
- def self.namespaces_in_contents(contents, file=__FILE__)
62
+ def self.namespaces_in_content(contents, file=__FILE__)
64
63
  old_constants = Thor::Base.subclasses.dup
65
64
  Thor::Base.subclasses.clear
66
65
 
@@ -74,6 +73,14 @@ class Thor
74
73
  new_constants
75
74
  end
76
75
 
76
+ # Returns the thor classes declared inside the given class.
77
+ #
78
+ def self.thor_classes_in(klass)
79
+ Thor::Base.subclasses.select do |subclass|
80
+ klass.constants.include?(subclass.name.gsub("#{klass.name}::", ''))
81
+ end
82
+ end
83
+
77
84
  # Receives a string and convert it to snake case. SnakeCase returns snake_case.
78
85
  #
79
86
  # ==== Parameters
@@ -88,6 +95,19 @@ class Thor
88
95
  return $+.downcase
89
96
  end
90
97
 
98
+ # Receives a string and convert it to camel case. camel_case returns CamelCase.
99
+ #
100
+ # ==== Parameters
101
+ # String
102
+ #
103
+ # ==== Returns
104
+ # String
105
+ #
106
+ def self.camel_case(str)
107
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
108
+ str.split('_').map { |i| i.capitalize }.join
109
+ end
110
+
91
111
  # Receives a namespace and tries to retrieve a Thor or Thor::Group class
92
112
  # from it. It first searches for a class using the all the given namespace,
93
113
  # if it's not found, removes the highest entry and searches for the class
@@ -116,7 +136,7 @@ class Thor
116
136
  # Thor::Error:: raised if the namespace evals to a class which does not
117
137
  # inherit from Thor or Thor::Group.
118
138
  #
119
- def self.namespace_to_thor_class(namespace, raise_if_nil=true)
139
+ def self.namespace_to_thor_class_and_task(namespace, raise_if_nil=true)
120
140
  klass, task_name = Thor::Util.find_by_namespace(namespace), nil
121
141
 
122
142
  if klass.nil? && namespace.include?(?:)
@@ -157,7 +177,7 @@ class Thor
157
177
  yaml.each do |k, v|
158
178
  next unless v[:constants] && v[:namespaces].nil?
159
179
  yaml_changed = true
160
- yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.constant_to_namespace(c)}
180
+ yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.namespace_from_thor_class(c)}
161
181
  end
162
182
 
163
183
  yaml_changed
@@ -0,0 +1,170 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'thor/actions'
3
+
4
+ describe Thor::Actions::CreateFile do
5
+ before(:each) do
6
+ ::FileUtils.rm_rf(destination_root)
7
+ end
8
+
9
+ def create_file(destination=nil, config={}, options={})
10
+ @base = MyCounter.new([1,2], options, { :destination_root => destination_root })
11
+ stub(@base).file_name { 'rdoc' }
12
+
13
+ @action = Thor::Actions::CreateFile.new(@base, destination, "CONFIGURATION",
14
+ { :verbose => !@silence }.merge(config))
15
+ end
16
+
17
+ def invoke!
18
+ capture(:stdout){ @action.invoke! }
19
+ end
20
+
21
+ def revoke!
22
+ capture(:stdout){ @action.revoke! }
23
+ end
24
+
25
+ def silence!
26
+ @silence = true
27
+ end
28
+
29
+ describe "#invoke!" do
30
+ it "creates a file" do
31
+ create_file("doc/config.rb")
32
+ invoke!
33
+ File.exists?(File.join(destination_root, "doc/config.rb")).must be_true
34
+ end
35
+
36
+ it "does not create a file if pretending" do
37
+ create_file("doc/config.rb", {}, :pretend => true)
38
+ invoke!
39
+ File.exists?(File.join(destination_root, "doc/config.rb")).must be_false
40
+ end
41
+
42
+ it "shows created status to the user" do
43
+ create_file("doc/config.rb")
44
+ invoke!.must == " create doc/config.rb\n"
45
+ end
46
+
47
+ it "does not show any information if log status is false" do
48
+ silence!
49
+ create_file("doc/config.rb")
50
+ invoke!.must be_empty
51
+ end
52
+
53
+ it "returns the destination" do
54
+ capture(:stdout) do
55
+ create_file("doc/config.rb").invoke!.must == File.join(destination_root, "doc/config.rb")
56
+ end
57
+ end
58
+
59
+ it "converts encoded instructions" do
60
+ create_file("doc/%file_name%.rb.tt")
61
+ invoke!
62
+ File.exists?(File.join(destination_root, "doc/rdoc.rb.tt")).must be_true
63
+ end
64
+
65
+ describe "when file exists" do
66
+ before(:each) do
67
+ create_file("doc/config.rb")
68
+ invoke!
69
+ end
70
+
71
+ describe "and is identical" do
72
+ it "shows identical status" do
73
+ create_file("doc/config.rb")
74
+ invoke!
75
+ invoke!.must == " identical doc/config.rb\n"
76
+ end
77
+ end
78
+
79
+ describe "and is not identical" do
80
+ before(:each) do
81
+ File.open(File.join(destination_root, 'doc/config.rb'), 'w'){ |f| f.write("FOO = 3") }
82
+ end
83
+
84
+ it "shows forced status to the user if force is given" do
85
+ create_file("doc/config.rb", {}, :force => true).must_not be_identical
86
+ invoke!.must == " force doc/config.rb\n"
87
+ end
88
+
89
+ it "shows skipped status to the user if skip is given" do
90
+ create_file("doc/config.rb", {}, :skip => true).must_not be_identical
91
+ invoke!.must == " skip doc/config.rb\n"
92
+ end
93
+
94
+ it "shows forced status to the user if force is configured" do
95
+ create_file("doc/config.rb", :force => true).must_not be_identical
96
+ invoke!.must == " force doc/config.rb\n"
97
+ end
98
+
99
+ it "shows skipped status to the user if skip is configured" do
100
+ create_file("doc/config.rb", :skip => true).must_not be_identical
101
+ invoke!.must == " skip doc/config.rb\n"
102
+ end
103
+
104
+ it "shows conflict status to ther user" do
105
+ create_file("doc/config.rb").must_not be_identical
106
+ mock($stdin).gets{ 's' }
107
+ file = File.join(destination_root, 'doc/config.rb')
108
+
109
+ content = invoke!
110
+ content.must =~ /conflict doc\/config\.rb/
111
+ content.must =~ /Overwrite #{file}\? \(enter "h" for help\) \[Ynaqdh\]/
112
+ content.must =~ /skip doc\/config\.rb/
113
+ end
114
+
115
+ it "creates the file if the file collision menu returns true" do
116
+ create_file("doc/config.rb")
117
+ mock($stdin).gets{ 'y' }
118
+ invoke!.must =~ /force doc\/config\.rb/
119
+ end
120
+
121
+ it "skips the file if the file collision menu returns false" do
122
+ create_file("doc/config.rb")
123
+ mock($stdin).gets{ 'n' }
124
+ invoke!.must =~ /skip doc\/config\.rb/
125
+ end
126
+
127
+ it "executes the block given to show file content" do
128
+ create_file("doc/config.rb")
129
+ mock($stdin).gets{ 'd' }
130
+ mock($stdin).gets{ 'n' }
131
+ mock(@base.shell).system(/diff -u/)
132
+ invoke!
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ describe "#revoke!" do
139
+ it "removes the destination file" do
140
+ create_file("doc/config.rb")
141
+ invoke!
142
+ revoke!
143
+ File.exists?(@action.destination).must be_false
144
+ end
145
+
146
+ it "does not raise an error if the file does not exist" do
147
+ create_file("doc/config.rb")
148
+ revoke!
149
+ File.exists?(@action.destination).must be_false
150
+ end
151
+ end
152
+
153
+ describe "#exists?" do
154
+ it "returns true if the destination file exists" do
155
+ create_file("doc/config.rb")
156
+ @action.exists?.must be_false
157
+ invoke!
158
+ @action.exists?.must be_true
159
+ end
160
+ end
161
+
162
+ describe "#identical?" do
163
+ it "returns true if the destination file and is identical" do
164
+ create_file("doc/config.rb")
165
+ @action.identical?.must be_false
166
+ invoke!
167
+ @action.identical?.must be_true
168
+ end
169
+ end
170
+ end