mmve 0.1.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,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: