mmve 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ /Gemfile-custom.rb
2
+ /Gemfile.lock
3
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ eval File.read('Gemfile-custom.rb') if File.exist?('Gemfile-custom.rb')
@@ -0,0 +1,11 @@
1
+ guard :cucumber, cli: '--format pretty --quiet' do
2
+ watch(%r{\Afeatures/.+\.feature\z})
3
+ watch(%r{\Afeatures/support/.+\.rb\z}) { 'features' }
4
+ watch(%r{\Afeatures/step_definitions/.+_steps\.rb\z}) { 'features' }
5
+ end
6
+
7
+ guard :rspec do
8
+ watch(%r{\Aspec/.+_spec\.rb\z})
9
+ watch(%r{\Alib/(.+)\.rb\z}) { |m| "spec/#{m[1]}_spec.rb" }
10
+ watch('spec/spec_helper.rb') { 'spec' }
11
+ end
data/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ Copyright 2013 Thibault Jouan. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are
5
+ met:
6
+
7
+ * Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in
12
+ the documentation and/or other materials provided with the
13
+ distribution.
14
+
15
+ * Neither the name of the software nor the names of its contributors
16
+ may be used to endorse or promote products derived from this
17
+ software without specific prior written permission.
18
+
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS "AS IS" AND
21
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS
24
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
30
+ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,11 @@
1
+ require 'cucumber/rake/task'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run all scenarios'
5
+ Cucumber::Rake::Task.new(:features)
6
+
7
+ desc 'Run all specs'
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+
11
+ task default: [:features, :spec]
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'mmve'
4
+
5
+ MMVE::CLI.new(ARGV).run!
@@ -0,0 +1,17 @@
1
+ Feature: Rename files
2
+
3
+ Background:
4
+ Given the following files exists:
5
+ | some_file |
6
+ | another_file |
7
+
8
+ Scenario: renames files given as arguments when the editor modify file paths
9
+ Given my editor is "ed"
10
+ When I run `mmve some_file another_file` interactively
11
+ And I type "%s/some/renamed/"
12
+ And I type "wq"
13
+ Then the exit status must be 0
14
+ Then the following files must exist:
15
+ | renamed_file |
16
+ | another_file |
17
+ And the file "some_file" must not exist
@@ -0,0 +1,3 @@
1
+ Given(/^my editor is "(.*?)"$/) do |editor|
2
+ set_env('EDITOR', editor)
3
+ end
@@ -0,0 +1,3 @@
1
+ Given(/^the following files exists:$/) do |paths|
2
+ paths.raw.flatten.each { |p| write_file p, '' }
3
+ end
@@ -0,0 +1,16 @@
1
+ module Cucumber
2
+ class Runtime
3
+ alias :old_step_match :step_match
4
+
5
+ def step_match(step_name, name_to_report = nil)
6
+ if step_name.include? ' must '
7
+ name_to_report = step_name.dup
8
+ step_name.gsub! ' must ', ' should '
9
+ end
10
+
11
+ old_step_match(step_name, name_to_report)
12
+ end
13
+ end
14
+ end
15
+
16
+ require 'aruba/cucumber'
@@ -0,0 +1,23 @@
1
+ require 'cucumber/formatter/pretty'
2
+
3
+ module Cucumber
4
+ module Ast
5
+ class DocString
6
+ alias :old_initialize :initialize
7
+
8
+ def initialize(string, content_type)
9
+ old_initialize(string + "\n", content_type)
10
+ end
11
+ end
12
+ end
13
+
14
+ module Formatter
15
+ class Pretty
16
+ alias :old_doc_string :doc_string
17
+
18
+ def doc_string(string)
19
+ old_doc_string(string.chomp)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ Feature: CLI usage
2
+
3
+ Scenario: prints the usage when -h argument is given
4
+ When I run `mmve -h`
5
+ Then the exit status must be 0
6
+ And the output must contain exactly:
7
+ """
8
+ Usage: mmve [ path ... ]
9
+ """
@@ -0,0 +1,4 @@
1
+ require 'mmve/cli'
2
+ require 'mmve/editor'
3
+ require 'mmve/renamer'
4
+ require 'mmve/version'
@@ -0,0 +1,31 @@
1
+ module MMVE
2
+ class CLI
3
+ USAGE = "Usage: #{File.basename $0} [ path ... ]"
4
+
5
+ def initialize(arguments, stdout = $stdout)
6
+ @arguments = arguments
7
+ @stdout = stdout
8
+ end
9
+
10
+ def run!
11
+ print_usage_and_exit if @arguments.include? '-h'
12
+ renamer.destinations = editor.edit renamer.sources
13
+ renamer.execute!
14
+ end
15
+
16
+ def editor
17
+ @editor ||= Editor.new(ENV['EDITOR'])
18
+ end
19
+
20
+ def renamer
21
+ @renamer ||= Renamer.new(@arguments)
22
+ end
23
+
24
+ private
25
+
26
+ def print_usage_and_exit
27
+ @stdout.puts USAGE
28
+ exit
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ module MMVE
2
+ class Editor
3
+ require 'tempfile'
4
+
5
+ def initialize(editor_command)
6
+ @editor_command = editor_command
7
+ end
8
+
9
+ def edit(paths)
10
+ edited_content = with_temp_file(paths * "\n" + "\n") { |f| edit_file f }
11
+ edited_content.split "\n"
12
+ end
13
+
14
+ def with_temp_file(content)
15
+ content_after = ''
16
+ Tempfile.open(File.basename($0 + '_')) do |f|
17
+ f.write content
18
+ f.rewind
19
+ yield f
20
+ content_after = f.read
21
+ end
22
+ content_after
23
+ end
24
+
25
+ def edit_file(file)
26
+ system(*@editor_command.split, file.path)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module MMVE
2
+ class Renamer
3
+ attr_reader :sources
4
+ attr_writer :destinations
5
+
6
+ def initialize(paths)
7
+ @sources = paths
8
+ @destinations = @sources.dup
9
+ end
10
+
11
+ def execute!
12
+ [@sources, @destinations].transpose.each do |e|
13
+ File.rename(*e) if e.uniq.count == 2
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module MMVE
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH << lib unless $LOAD_PATH.include? lib
3
+ require 'mmve/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'mmve'
7
+ s.version = MMVE::VERSION
8
+ s.summary = "mmve-#{MMVE::VERSION}"
9
+ s.description = 'Mass MV Editor: move files with your favourite $EDITOR'
10
+ s.homepage = 'https://rubygems.org/gems/mmve'
11
+
12
+ s.authors = 'Thibault Jouan'
13
+ s.email = 'tj@a13.fr'
14
+
15
+ s.files = `git ls-files`.split $/
16
+ s.test_files = s.files.grep /\A(spec|features)\//
17
+ s.executables = s.files.grep(/\Abin\//) { |f| File.basename(f) }
18
+
19
+
20
+ s.add_dependency 'net-ssh'
21
+
22
+ s.add_development_dependency 'rspec'
23
+ s.add_development_dependency 'cucumber'
24
+ s.add_development_dependency 'aruba'
25
+ s.add_development_dependency 'rake'
26
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ describe MMVE::CLI do
4
+ include ExitHelpers
5
+
6
+ let(:arguments) { [:some, :arguments] }
7
+ let(:stdout) { StringIO.new }
8
+ subject(:cli) { MMVE::CLI.new(arguments, stdout) }
9
+
10
+ describe '#initialize' do
11
+ it 'assigns the arguments' do
12
+ expect(cli.instance_eval { @arguments }).to eq arguments
13
+ end
14
+
15
+ it 'assigns the standard output' do
16
+ expect(cli.instance_eval { @stdout }).to eq stdout
17
+ end
18
+
19
+ context 'when no stdout is specified' do
20
+ subject(:cli) { MMVE::CLI.new(arguments) }
21
+
22
+ it 'assigns $stdout as a default standard output' do
23
+ expect(cli.instance_eval { @stdout }).to be $stdout
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#run!' do
29
+ it 'edits the renamer source paths' do
30
+ renamer = double('renamer').as_null_object
31
+ allow(cli).to receive(:renamer) { renamer }
32
+ expect(cli.editor).to receive(:edit).with(cli.renamer.sources)
33
+ cli.run!
34
+ end
35
+
36
+ it 'assigns the edited source paths as the renamer destination paths' do
37
+ editor = double('editor')
38
+ allow(cli).to receive(:editor) { editor }
39
+ allow(editor).to receive(:edit) { arguments }
40
+ expect(cli.renamer).to receive(:destinations=).with(arguments)
41
+ cli.run!
42
+ end
43
+
44
+ it 'executes the renamer' do
45
+ editor = double('editor').as_null_object
46
+ allow(cli).to receive(:editor) { editor }
47
+ expect(cli.renamer).to receive(:execute!)
48
+ cli.run!
49
+ end
50
+
51
+ context 'when one of the arguments is -h' do
52
+ let(:arguments) { %w[-h] }
53
+
54
+ it 'prints the usage' do
55
+ trap_exit { cli.run! }
56
+ expect(stdout.string).to eq MMVE::CLI::USAGE + "\n"
57
+ end
58
+
59
+ it 'exits successfully' do
60
+ expect { cli.run! }.to raise_error(SystemExit) do |e|
61
+ expect(e.code).to be 0
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ describe '#editor' do
68
+ it 'builds an editor with current $EDITOR' do
69
+ expect(MMVE::Editor).to receive(:new).with(ENV['EDITOR'])
70
+ cli.editor
71
+ end
72
+
73
+ it 'returns the editor' do
74
+ editor = double('editor')
75
+ allow(MMVE::Editor).to receive(:new) { editor }
76
+ expect(cli.editor).to be editor
77
+ end
78
+ end
79
+
80
+ describe '#renamer' do
81
+ it 'builds a renamer with the paths given as argument' do
82
+ expect(MMVE::Renamer).to receive(:new).with(arguments)
83
+ cli.renamer
84
+ end
85
+
86
+ it 'returns the renamer' do
87
+ renamer = double('renamer')
88
+ allow(MMVE::Renamer).to receive(:new) { renamer }
89
+ expect(cli.renamer).to be renamer
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe MMVE::Editor do
4
+ let(:editor_command) { 'some editor command' }
5
+ subject(:editor) { MMVE::Editor.new(editor_command) }
6
+
7
+ describe '#initialize' do
8
+ it 'assigns the editor command' do
9
+ expect(editor.instance_eval { @editor_command }).to eq editor_command
10
+ end
11
+ end
12
+
13
+ describe '#edit' do
14
+ let(:paths) { %w[some_path other_path] }
15
+ let(:paths_str) { paths * "\n" + "\n" }
16
+
17
+ it 'creates a temporary file with the paths as content' do
18
+ expect(editor)
19
+ .to receive(:with_temp_file).with(paths_str).and_return(paths_str)
20
+ editor.edit(paths)
21
+ end
22
+
23
+ it 'sends the edit_file message with the temporary file' do
24
+ expect(editor).to receive(:edit_file).with(any_args())
25
+ editor.edit(paths)
26
+ end
27
+
28
+ it 'returns the edited file paths as a list' do
29
+ allow(editor).to receive(:with_temp_file) { paths_str }
30
+ expect(editor.edit(paths)).to eq paths
31
+ end
32
+ end
33
+
34
+ describe '#with_temp_file' do
35
+ let(:content) { "some_content\n" }
36
+
37
+ it 'opens a temporary file' do
38
+ expect(Tempfile).to receive(:open).with('rspec_')
39
+ editor.with_temp_file(content) { }
40
+ end
41
+
42
+ it 'it writes the content to the file' do
43
+ file = double('file').as_null_object
44
+ allow(Tempfile).to receive(:open).and_yield(file)
45
+ expect(file).to receive(:write).with(content)
46
+ editor.with_temp_file(content) { }
47
+ end
48
+
49
+ it 'yields the block with the file as argument' do
50
+ file = double('file').as_null_object
51
+ allow(Tempfile).to receive(:open).and_yield(file)
52
+ expect { |b| editor.with_temp_file(content, &b) }.to yield_with_args(file)
53
+ end
54
+
55
+ it 'returns the temporary file content after yielding the block' do
56
+ new_content = "new_content\n"
57
+ expect(
58
+ editor.with_temp_file(content) { |f| File.write f.path, new_content }
59
+ ).to eq new_content
60
+ end
61
+ end
62
+
63
+ describe '#edit_file' do
64
+ let(:file) { double('file', path: 'some_path') }
65
+
66
+ it 'executes the editor command with the file path as argument' do
67
+ expect(editor).to receive(:system).with(*editor_command.split, file.path)
68
+ editor.edit_file(file)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe MMVE::Renamer do
4
+ let(:sources) { %w[some_path other_path] }
5
+ let(:destinations) { %w[renamed_path other_path] }
6
+ subject(:renamer) { MMVE::Renamer.new(sources) }
7
+
8
+ describe '#initialize' do
9
+ it 'assigns the source paths' do
10
+ expect(renamer.instance_eval { @sources }).to eq sources
11
+ end
12
+
13
+ it 'assigns destination paths equivalent to source paths' do
14
+ expect(renamer.instance_eval { @destinations }).to eq sources
15
+ end
16
+
17
+ it 'copies the assignated destinations paths' do
18
+ expect(renamer.instance_eval { @destinations }).to_not be sources
19
+ end
20
+ end
21
+
22
+ describe '#sources' do
23
+ it 'returns the source paths' do
24
+ expect(renamer.sources).to eq sources
25
+ end
26
+ end
27
+
28
+ describe '#destinations=' do
29
+ it 'assigns the destination paths' do
30
+ renamer.destinations = destinations
31
+ expect(renamer.instance_eval { @destinations }).to eq destinations
32
+ end
33
+ end
34
+
35
+ describe '#execute!' do
36
+ context 'when destinations differs from sources' do
37
+ before do
38
+ renamer.destinations = destinations
39
+ end
40
+
41
+ it 'renames the sources to the destinations' do
42
+ expect(File)
43
+ .to receive(:rename).with(sources.first, destinations.first)
44
+ renamer.execute!
45
+ end
46
+ end
47
+
48
+ context 'when destinations are the same as sources' do
49
+ let(:destinations) { sources }
50
+
51
+ it 'renames nothing' do
52
+ expect(File).not_to receive(:rename)
53
+ renamer.execute!
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ require 'mmve'
2
+
3
+ require 'support/exit_helpers'
@@ -0,0 +1,6 @@
1
+ module ExitHelpers
2
+ def trap_exit
3
+ yield
4
+ rescue SystemExit
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mmve
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thibault Jouan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-ssh
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: cucumber
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: aruba
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: ! 'Mass MV Editor: move files with your favourite $EDITOR'
95
+ email: tj@a13.fr
96
+ executables:
97
+ - mmve
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - Gemfile
103
+ - Guardfile
104
+ - LICENSE
105
+ - Rakefile
106
+ - bin/mmve
107
+ - features/rename.feature
108
+ - features/step_definitions/editor_steps.rb
109
+ - features/step_definitions/filesystem_steps.rb
110
+ - features/support/env_aruba.rb
111
+ - features/support/env_cucumber-doc_string.rb
112
+ - features/usage.feature
113
+ - lib/mmve.rb
114
+ - lib/mmve/cli.rb
115
+ - lib/mmve/editor.rb
116
+ - lib/mmve/renamer.rb
117
+ - lib/mmve/version.rb
118
+ - mmve.gemspec
119
+ - spec/mmve/cli_spec.rb
120
+ - spec/mmve/editor_spec.rb
121
+ - spec/mmve/renamer_spec.rb
122
+ - spec/spec_helper.rb
123
+ - spec/support/exit_helpers.rb
124
+ homepage: https://rubygems.org/gems/mmve
125
+ licenses: []
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 1.8.23
145
+ signing_key:
146
+ specification_version: 3
147
+ summary: mmve-0.1.0
148
+ test_files:
149
+ - features/rename.feature
150
+ - features/step_definitions/editor_steps.rb
151
+ - features/step_definitions/filesystem_steps.rb
152
+ - features/support/env_aruba.rb
153
+ - features/support/env_cucumber-doc_string.rb
154
+ - features/usage.feature
155
+ - spec/mmve/cli_spec.rb
156
+ - spec/mmve/editor_spec.rb
157
+ - spec/mmve/renamer_spec.rb
158
+ - spec/spec_helper.rb
159
+ - spec/support/exit_helpers.rb
160
+ has_rdoc: