dir_sync 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.rdoc CHANGED
@@ -1,4 +1,10 @@
1
- = 0.1.0
1
+ = 0.1.1 Sun 29 Jan 2012 14:40:03 EST
2
2
 
3
- Fri 27 Jan 2012 20:57:18 EST
4
- Initial release
3
+ * moved everything into a DirSync module to prevent any namespace collisions.
4
+ * explicitly stated support for ruby 1.9 only in gem specification
5
+ * changed drain script to accept any number of parameters
6
+
7
+ = 0.1.0 Fri 27 Jan 2012 20:57:18 EST
8
+
9
+ * initial release
10
+ * performs multidirectional synchronisation for any number of local directories
data/README.rdoc CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://secure.travis-ci.org/markryall/dir_sync.png)](http://travis-ci.org/markryall/dir_sync)
2
+
1
3
  = dir_sync
2
4
 
3
5
  Produce a script to synchronise two directories. Kind of like unison but a bit simpler.
@@ -12,10 +14,13 @@ in subsequent synchronisations to detect deletions. You can then review the com
12
14
  before executing them (in case they seem likely to do something undesirable). Note that
13
15
  files that differ only in modification time will be overwritten with the most recent one.
14
16
 
15
- drain script
17
+ drain script1 script2
16
18
 
17
- This simply runs each line in the specified script. As each line is executed, a new script is
18
- produced. The purpose of this is to allow the script to be interrupted and able to resume.
19
+ This simply runs each line in the specified scripts rewriting the script as each
20
+ command is succesfully executed. This is just so if the script is interrupted or
21
+ killed, you can resume. Note that the new file will be written to a temp file first
22
+ (in case the rewriting is interrupted), each command must appear on a single line
23
+ and it is likely that the command interrupted will be rexecuted.
19
24
 
20
25
  Together, these scripts can be used as follows:
21
26
 
data/Rakefile CHANGED
@@ -1,12 +1,20 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
- task :default => :test
4
- task :test => [:spec, :features]
3
+ task :default => [:spec, :cucumber]
5
4
 
6
- task :spec do
7
- sh 'rspec spec'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new
7
+
8
+ require 'cucumber/rake/task'
9
+ namespace :cucumber do
10
+ Cucumber::Rake::Task.new :strict do |task|
11
+ task.cucumber_opts = '-p strict'
12
+ end
13
+
14
+ Cucumber::Rake::Task.new :wip do |task|
15
+ task.cucumber_opts = '-p wip'
16
+ end
8
17
  end
9
18
 
10
- task :features do
11
- sh 'cucumber'
12
- end
19
+ desc 'Run all Cucumber features'
20
+ task :cucumber => ['cucumber:wip', 'cucumber:strict']
data/bin/dir_sync CHANGED
@@ -1,13 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  $: << File.dirname(__FILE__)+'/../lib'
4
-
5
- require 'synchroniser'
6
-
7
- if ARGV.size < 3
8
- puts "usage: #{__FILE__} name directory1 directory2 ..."
9
- puts " set DEBUG for verbose output"
10
- exit 1
11
- end
12
-
13
- Synchroniser.iterate *ARGV
3
+ require 'dir_sync'
4
+ DirSync.sync *ARGV
data/bin/drain CHANGED
@@ -1,18 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- if ARGV.empty?
4
- puts "Runs a set of single line commands one by one (so that you can interrupt and the script will resume)"
5
- puts "usage: #{__FILE__} script"
6
- exit 1
7
- end
8
-
9
- file = ARGV.shift
10
- lines = File.readlines(file).map {|s| s.chomp.strip }.select {|s| !s.empty? and !s.start_with?('#')}
11
-
12
- loop do
13
- break if lines.empty?
14
- lines.shift.tap {|s| puts "> #{s}" }.tap {|s| puts `#{s}` }
15
- File.open("#{file}.tmp", 'w') {|f| f.puts lines.join "\n" }
16
- `mv #{file}.tmp #{file}`
17
- end
18
- `rm #{file}`
2
+ $: << File.dirname(__FILE__)+'/../lib'
3
+ require 'dir_sync'
4
+ DirSync.drain *ARGV
@@ -0,0 +1,76 @@
1
+ Feature: Synchonising folders
2
+ As a practical, intelligent and charismatic person
3
+ I want to synchronise folders
4
+ So that I can keep multiple backup copies of my work
5
+
6
+ Scenario: Syncing two empty directories
7
+ Given a directory named "a"
8
+ And a directory named "b"
9
+ When I successfully run `dir_sync test a b`
10
+ Then the stdout should contain exactly:
11
+ """
12
+
13
+ """
14
+
15
+ Scenario: Syncing two non existant directories
16
+ When I successfully run `dir_sync test a b`
17
+ Then the stdout should contain exactly:
18
+ """
19
+
20
+ """
21
+ And a directory named "a" should exist
22
+ And a directory named "b" should exist
23
+
24
+ Scenario: Syncing a single file from left to right
25
+ Given a directory named "a"
26
+ And I write to "a/readme.txt" with:
27
+ """
28
+ The content
29
+ """
30
+ When I successfully run `dir_sync test a b`
31
+ Then the stdout should contain exactly:
32
+ """
33
+ cp -p "a/readme.txt" "b/readme.txt"
34
+
35
+ """
36
+
37
+ Scenario: Syncing a single file from right to left
38
+ Given a directory named "b"
39
+ And I write to "b/readme.txt" with:
40
+ """
41
+ The content
42
+ """
43
+ When I successfully run `dir_sync test a b`
44
+ Then the stdout should contain exactly:
45
+ """
46
+ cp -p "b/readme.txt" "a/readme.txt"
47
+
48
+ """
49
+
50
+ Scenario: Do nothing when files are already in sync with history
51
+ Given the file system:
52
+ | path | time |
53
+ | a/readme.txt | 1000 |
54
+ | b/readme.txt | 1000 |
55
+ And past synchronisation history:
56
+ | path | time |
57
+ | readme.txt | 1000 |
58
+ When I successfully run `dir_sync test a b`
59
+ Then the stdout should contain exactly:
60
+ """
61
+
62
+ """
63
+
64
+ Scenario: Detecting a deletion from past history
65
+ Given the file system:
66
+ | path | time |
67
+ | b/readme.txt | 1000 |
68
+ And past synchronisation history:
69
+ | path | time |
70
+ | readme.txt | 1000 |
71
+ When I successfully run `dir_sync test a b`
72
+ Then the stdout should contain exactly:
73
+ """
74
+ rm "b/readme.txt"
75
+
76
+ """
@@ -0,0 +1,22 @@
1
+ Given /^the file system:$/ do |table|
2
+ `mkdir -p tmp/aruba`
3
+ Dir.chdir 'tmp/aruba' do
4
+ table.hashes.each do |row|
5
+ path, time = row['path'], row['time'].to_i
6
+ formatted_time = Time.at(time).strftime '%Y%m%d%H%M.%S'
7
+ `mkdir -p #{File.dirname path}`
8
+ `touch -t #{formatted_time} #{path}`
9
+ end
10
+ end
11
+ end
12
+
13
+ Given /^past synchronisation history:$/ do |table|
14
+ home = File.expand_path '~'
15
+ `mkdir #{home}/.dir_sync`
16
+ File.open("#{home}/.dir_sync/test", 'w') do |f|
17
+ table.hashes.each do |row|
18
+ path, time = row['path'], row['time'].to_i
19
+ f.puts "#{path}:#{time}"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ require 'aruba/cucumber'
2
+
3
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
4
+
5
+ Before do
6
+ `rm ~/.dir_sync/test`
7
+ end
@@ -1,4 +1,7 @@
1
- class ChangeLogFileSystem
1
+ module DirSync
2
+ end
3
+
4
+ class DirSync::ChangeLogFileSystem
2
5
  def initialize io
3
6
  @io = io
4
7
  end
@@ -12,4 +15,4 @@ class ChangeLogFileSystem
12
15
  def rm path
13
16
  @io.puts "rm \"#{path}\""
14
17
  end
15
- end
18
+ end
@@ -1,4 +1,7 @@
1
- class ChangeResolver
1
+ module DirSync
2
+ end
3
+
4
+ class DirSync::ChangeResolver
2
5
  def debug message
3
6
  puts "# #{message}" if ENV['DEBUG']
4
7
  end
@@ -71,4 +74,4 @@ private
71
74
  exps.each { |exp| return exp unless exp == 0 }
72
75
  0
73
76
  end
74
- end
77
+ end
@@ -1,4 +1,7 @@
1
- class HistoricalTraverser
1
+ module DirSync
2
+ end
3
+
4
+ class DirSync::HistoryTraverser
2
5
  attr_reader :name, :ts, :base, :description
3
6
  REGEXP = /:(\d+)$/
4
7
 
@@ -43,4 +46,4 @@ class HistoricalTraverser
43
46
  def empty?
44
47
  @description.nil?
45
48
  end
46
- end
49
+ end
@@ -1,6 +1,9 @@
1
1
  require 'pathname'
2
2
 
3
- class Traverser
3
+ module DirSync
4
+ end
5
+
6
+ class DirSync::Traverser
4
7
  attr_reader :base
5
8
 
6
9
  TOLERANCE=5
@@ -57,4 +60,4 @@ class Traverser
57
60
  def equivalent? traverser
58
61
  name == traverser.name and (ts - traverser.ts).abs <= TOLERANCE
59
62
  end
60
- end
63
+ end
data/lib/dir_sync.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'dir_sync/change_log_file_system'
2
+ require 'dir_sync/traverser'
3
+ require 'dir_sync/history_traverser'
4
+ require 'dir_sync/change_resolver'
5
+
6
+ module DirSync
7
+ def self.sync name, *paths
8
+ if paths.count < 2
9
+ puts "usage: #{__FILE__} name directory1 directory2 ..."
10
+ puts " set DEBUG for verbose output"
11
+ exit 1
12
+ end
13
+ file_system = DirSync::ChangeLogFileSystem.new $stdout
14
+ traversers = paths.map {|path| DirSync::Traverser.new path, file_system }
15
+ history = DirSync::HistoryTraverser.new name
16
+ resolver = DirSync::ChangeResolver.new history, *traversers
17
+ loop { break unless resolver.iterate }
18
+ history.close
19
+ end
20
+
21
+ def self.drain *paths
22
+ if paths.empty?
23
+ puts "usage: #{__FILE__} *scripts"
24
+ puts " Runs a set of single line commands one by one (so that you can interrupt and the script will resume)"
25
+ exit 1
26
+ end
27
+
28
+ paths.each do |path|
29
+ lines = File.readlines(path).map {|s| s.chomp.strip }.select {|s| !s.empty? and !s.start_with?('#')}
30
+
31
+ loop do
32
+ break if lines.empty?
33
+ lines.shift.tap {|s| puts "> #{s}" }.tap {|s| puts `#{s}` }
34
+ File.open("#{path}.tmp", 'w') {|f| f.puts lines.join "\n" }
35
+ `mv #{path}.tmp #{path}`
36
+ end
37
+ `rm #{path}`
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ $: << File.dirname(__FILE__)+'/../lib'
2
+
3
+ require 'dir_sync/change_log_file_system'
4
+
5
+ describe DirSync::ChangeLogFileSystem do
6
+ let(:io) { stub 'io' }
7
+ let(:file_system) { DirSync::ChangeLogFileSystem.new io }
8
+
9
+ it 'should cp a file' do
10
+ io.should_receive(:puts).with 'cp -p "from" "to"'
11
+ file_system.cp 'from', 'to'
12
+ end
13
+
14
+ it 'should mkdir for cp when parent does not exist' do
15
+ io.should_receive(:puts).with 'mkdir -p "a/b/c"'
16
+ io.should_receive(:puts).with 'cp -p "from" "a/b/c/to"'
17
+ file_system.cp 'from', 'a/b/c/to'
18
+ end
19
+
20
+ it 'should rm a file' do
21
+ io.should_receive(:puts).with 'rm "file"'
22
+ file_system.rm 'file'
23
+ end
24
+ end
@@ -1,11 +1,11 @@
1
1
  $: << File.dirname(__FILE__)+'/../lib'
2
2
 
3
- require 'change_resolver'
3
+ require 'dir_sync/change_resolver'
4
4
 
5
- describe ChangeResolver do
5
+ describe DirSync::ChangeResolver do
6
6
  let(:history) { stub 'history', name: nil, base: nil, description: nil, report: nil, advance: nil }
7
7
  let(:traversers) { [] }
8
- let(:resolver) { ChangeResolver.new history, *traversers }
8
+ let(:resolver) { DirSync::ChangeResolver.new history, *traversers }
9
9
 
10
10
  def stub_history hash
11
11
  hash.each do |meth,ret|
@@ -0,0 +1,25 @@
1
+ $: << File.dirname(__FILE__)+'/../lib'
2
+
3
+ require 'dir_sync/history_traverser'
4
+
5
+ describe DirSync::HistoryTraverser do
6
+ let(:dir_sync_path) { stub 'dir_sync_path', mkpath: nil }
7
+ let(:new_path) { stub 'new_path' }
8
+ let(:traverser) { DirSync::HistoryTraverser.new 'test' }
9
+
10
+ before do
11
+ File.stub!(:expand_path).with('~').and_return '/home/user'
12
+ Pathname.stub!(:new).with('/home/user/.dir_sync').and_return dir_sync_path
13
+ File.stub!(:open).with('/home/user/.dir_sync/test.new', 'w').and_return new_path
14
+ end
15
+
16
+ it 'should create the ~/.dir_sync directory' do
17
+ dir_sync_path.should_receive :mkpath
18
+ traverser
19
+ end
20
+
21
+ it 'should open the new file for capturing new state' do
22
+ File.should_receive(:open).with '/home/user/.dir_sync/test.new', 'w'
23
+ traverser
24
+ end
25
+ end
@@ -1,11 +1,11 @@
1
1
  $: << File.dirname(__FILE__)+'/../lib'
2
2
 
3
- require 'traverser'
3
+ require 'dir_sync/traverser'
4
4
 
5
- describe Traverser do
5
+ describe DirSync::Traverser do
6
6
  let(:pathname) { stub 'pathname', mkpath: nil, find: nil}
7
7
  let(:file_system) { stub 'file_system'}
8
- let(:traverser) { Traverser.new 'a', file_system }
8
+ let(:traverser) { DirSync::Traverser.new 'a', file_system }
9
9
 
10
10
  before do
11
11
  Pathname.should_receive(:new).with('a').and_return pathname
@@ -55,4 +55,4 @@ describe Traverser do
55
55
  file_system.should_receive(:cp).with 'a/1.txt', 'b/1.txt'
56
56
  traverser.cp other_traverser
57
57
  end
58
- end
58
+ end
@@ -1,8 +1,8 @@
1
1
  $: << File.dirname(__FILE__)+'/../lib'
2
2
 
3
- require 'synchroniser'
3
+ require 'dir_sync'
4
4
 
5
- describe Synchroniser do
5
+ describe DirSync do
6
6
  let(:file_system) { stub 'file_system' }
7
7
  let(:resolver) { stub 'resolver'}
8
8
  let(:history) { stub 'history', close: nil }
@@ -10,22 +10,22 @@ describe Synchroniser do
10
10
  let(:traverser_b) { stub 'traverser_b' }
11
11
 
12
12
  before do
13
- ChangeLogFileSystem.should_receive(:new).with($stdout).and_return file_system
14
- HistoricalTraverser.should_receive(:new).with('test').and_return history
15
- Traverser.should_receive(:new).with('a', file_system).and_return traverser_a
16
- Traverser.should_receive(:new).with('b', file_system).and_return traverser_b
17
- ChangeResolver.should_receive(:new).with(history, traverser_a, traverser_b).and_return resolver
13
+ DirSync::ChangeLogFileSystem.should_receive(:new).with($stdout).and_return file_system
14
+ DirSync::HistoryTraverser.should_receive(:new).with('test').and_return history
15
+ DirSync::Traverser.should_receive(:new).with('a', file_system).and_return traverser_a
16
+ DirSync::Traverser.should_receive(:new).with('b', file_system).and_return traverser_b
17
+ DirSync::ChangeResolver.should_receive(:new).with(history, traverser_a, traverser_b).and_return resolver
18
18
  end
19
19
 
20
20
  it 'should call iterate once resolved if it returns false' do
21
21
  resolver.should_receive(:iterate).and_return false
22
- Synchroniser.iterate 'test', 'a', 'b'
22
+ DirSync.sync 'test', 'a', 'b'
23
23
  end
24
24
 
25
25
  it 'should repeatedly call iterate on resolved until it returns false' do
26
26
  resolver.should_receive(:iterate).and_return true
27
27
  resolver.should_receive(:iterate).and_return true
28
28
  resolver.should_receive(:iterate).and_return false
29
- Synchroniser.iterate 'test', 'a', 'b'
29
+ DirSync.sync 'test', 'a', 'b'
30
30
  end
31
- end
31
+ end
metadata CHANGED
@@ -1,19 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dir_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mark Ryall
9
+ - James Ottaway
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2012-01-27 00:00:00.000000000Z
13
+ date: 2012-01-29 00:00:00.000000000Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: rake
16
- requirement: &70177781836300 !ruby/object:Gem::Requirement
17
+ requirement: &70170387871540 !ruby/object:Gem::Requirement
17
18
  none: false
18
19
  requirements:
19
20
  - - ~>
@@ -21,10 +22,10 @@ dependencies:
21
22
  version: '0'
22
23
  type: :development
23
24
  prerelease: false
24
- version_requirements: *70177781836300
25
+ version_requirements: *70170387871540
25
26
  - !ruby/object:Gem::Dependency
26
27
  name: rspec
27
- requirement: &70177781835000 !ruby/object:Gem::Requirement
28
+ requirement: &70170387870300 !ruby/object:Gem::Requirement
28
29
  none: false
29
30
  requirements:
30
31
  - - ~>
@@ -32,10 +33,10 @@ dependencies:
32
33
  version: '2'
33
34
  type: :development
34
35
  prerelease: false
35
- version_requirements: *70177781835000
36
+ version_requirements: *70170387870300
36
37
  - !ruby/object:Gem::Dependency
37
38
  name: guard
38
- requirement: &70177781834120 !ruby/object:Gem::Requirement
39
+ requirement: &70170387869340 !ruby/object:Gem::Requirement
39
40
  none: false
40
41
  requirements:
41
42
  - - ! '>='
@@ -43,10 +44,10 @@ dependencies:
43
44
  version: '0'
44
45
  type: :development
45
46
  prerelease: false
46
- version_requirements: *70177781834120
47
+ version_requirements: *70170387869340
47
48
  - !ruby/object:Gem::Dependency
48
49
  name: guard-rspec
49
- requirement: &70177781832600 !ruby/object:Gem::Requirement
50
+ requirement: &70170387868340 !ruby/object:Gem::Requirement
50
51
  none: false
51
52
  requirements:
52
53
  - - ! '>='
@@ -54,10 +55,21 @@ dependencies:
54
55
  version: '0'
55
56
  type: :development
56
57
  prerelease: false
57
- version_requirements: *70177781832600
58
+ version_requirements: *70170387868340
59
+ - !ruby/object:Gem::Dependency
60
+ name: growl
61
+ requirement: &70170387867260 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *70170387867260
58
70
  - !ruby/object:Gem::Dependency
59
71
  name: aruba
60
- requirement: &70177781831880 !ruby/object:Gem::Requirement
72
+ requirement: &70170387866340 !ruby/object:Gem::Requirement
61
73
  none: false
62
74
  requirements:
63
75
  - - ! '>='
@@ -65,8 +77,8 @@ dependencies:
65
77
  version: '0'
66
78
  type: :development
67
79
  prerelease: false
68
- version_requirements: *70177781831880
69
- description: ! 'Bidirectional directory synchronisation for any number of directories
80
+ version_requirements: *70170387866340
81
+ description: ! 'Multidirectional directory synchronisation for any number of directories
70
82
 
71
83
  '
72
84
  email: mark@ryall.name
@@ -76,21 +88,26 @@ executables:
76
88
  extensions: []
77
89
  extra_rdoc_files: []
78
90
  files:
79
- - lib/change_log_file_system.rb
80
- - lib/change_resolver.rb
81
- - lib/historical_traverser.rb
82
- - lib/synchroniser.rb
83
- - lib/traverser.rb
84
- - spec/change_resolver_spec.rb
85
- - spec/synchroniser_spec.rb
86
- - spec/traverser_spec.rb
87
91
  - bin/dir_sync
88
92
  - bin/drain
89
- - README.rdoc
90
- - MIT-LICENSE
93
+ - features/backup.feature
94
+ - features/step_definitions/dir_sync_steps.rb
95
+ - features/support/env.rb
96
+ - lib/dir_sync/change_log_file_system.rb
97
+ - lib/dir_sync/change_resolver.rb
98
+ - lib/dir_sync/history_traverser.rb
99
+ - lib/dir_sync/traverser.rb
100
+ - lib/dir_sync.rb
101
+ - spec/dir_sync/change_log_file_system_spec.rb
102
+ - spec/dir_sync/change_resolver_spec.rb
103
+ - spec/dir_sync/history_traverser_spec.rb
104
+ - spec/dir_sync/traverser_spec.rb
105
+ - spec/dir_sync_spec.rb
106
+ - .gemtest
91
107
  - HISTORY.rdoc
108
+ - MIT-LICENSE
92
109
  - Rakefile
93
- - .gemtest
110
+ - README.rdoc
94
111
  homepage: http://github.com/markryall/dir_sync
95
112
  licenses: []
96
113
  post_install_message:
@@ -102,10 +119,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
102
119
  requirements:
103
120
  - - ! '>='
104
121
  - !ruby/object:Gem::Version
105
- version: '0'
106
- segments:
107
- - 0
108
- hash: -2227443201844382087
122
+ version: '1.9'
109
123
  required_rubygems_version: !ruby/object:Gem::Requirement
110
124
  none: false
111
125
  requirements:
@@ -114,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
128
  version: '0'
115
129
  segments:
116
130
  - 0
117
- hash: -2227443201844382087
131
+ hash: -3613376495215102755
118
132
  requirements: []
119
133
  rubyforge_project:
120
134
  rubygems_version: 1.8.10
data/lib/synchroniser.rb DELETED
@@ -1,15 +0,0 @@
1
- require 'traverser'
2
- require 'historical_traverser'
3
- require 'change_resolver'
4
- require 'change_log_file_system'
5
-
6
- module Synchroniser
7
- def self.iterate name, *paths
8
- file_system = ChangeLogFileSystem.new $stdout
9
- traversers = paths.map {|path| Traverser.new path, file_system }
10
- history = HistoricalTraverser.new name
11
- resolver = ChangeResolver.new history, *traversers
12
- loop { break unless resolver.iterate }
13
- history.close
14
- end
15
- end