synqa 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -5
- data/Gemfile +16 -16
- data/Gemfile.lock +4 -3
- data/README.rdoc +43 -43
- data/Rakefile +53 -53
- data/VERSION +1 -1
- data/_project.el +9 -9
- data/examples/synqa-useage.rb +37 -37
- data/lib/based.rb +177 -177
- data/lib/synqa.rb +1052 -1005
- data/test/helper.rb +18 -18
- data/test/test_based.rb +114 -114
- data/test/test_synqa.rb +37 -37
- metadata +22 -23
data/.document
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
lib/**/*.rb
|
2
|
-
bin/*
|
3
|
-
-
|
4
|
-
features/**/*.feature
|
5
|
-
LICENSE.txt
|
1
|
+
lib/**/*.rb
|
2
|
+
bin/*
|
3
|
+
-
|
4
|
+
features/**/*.feature
|
5
|
+
LICENSE.txt
|
data/Gemfile
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
source "http://rubygems.org"
|
2
|
-
# Add dependencies required to use your gem here.
|
3
|
-
# Example:
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
5
|
-
|
6
|
-
gem "net-ssh", ">= 2.0"
|
7
|
-
gem "net-scp", ">= 1.0"
|
8
|
-
|
9
|
-
# Add dependencies to develop your gem here.
|
10
|
-
# Include everything needed to run rake, tests, features, etc.
|
11
|
-
group :development do
|
12
|
-
gem "shoulda", ">= 0"
|
13
|
-
gem "bundler", "~> 1.0.0"
|
14
|
-
gem "jeweler", "~> 1.
|
15
|
-
gem "rcov", ">= 0"
|
16
|
-
end
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem "net-ssh", ">= 2.0"
|
7
|
+
gem "net-scp", ">= 1.0"
|
8
|
+
|
9
|
+
# Add dependencies to develop your gem here.
|
10
|
+
# Include everything needed to run rake, tests, features, etc.
|
11
|
+
group :development do
|
12
|
+
gem "shoulda", ">= 0"
|
13
|
+
gem "bundler", "~> 1.0.0"
|
14
|
+
gem "jeweler", "~> 1.6.2"
|
15
|
+
gem "rcov", ">= 0"
|
16
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -2,8 +2,8 @@ GEM
|
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
4
|
git (1.2.5)
|
5
|
-
jeweler (1.
|
6
|
-
bundler (~> 1.0
|
5
|
+
jeweler (1.6.2)
|
6
|
+
bundler (~> 1.0)
|
7
7
|
git (>= 1.2.5)
|
8
8
|
rake
|
9
9
|
net-scp (1.0.4)
|
@@ -14,11 +14,12 @@ GEM
|
|
14
14
|
shoulda (2.11.3)
|
15
15
|
|
16
16
|
PLATFORMS
|
17
|
+
ruby
|
17
18
|
x86-mingw32
|
18
19
|
|
19
20
|
DEPENDENCIES
|
20
21
|
bundler (~> 1.0.0)
|
21
|
-
jeweler (~> 1.
|
22
|
+
jeweler (~> 1.6.2)
|
22
23
|
net-scp (>= 1.0)
|
23
24
|
net-ssh (>= 2.0)
|
24
25
|
rcov
|
data/README.rdoc
CHANGED
@@ -1,43 +1,43 @@
|
|
1
|
-
= synqa
|
2
|
-
|
3
|
-
*Synqa* is a simple file syncing tool that works over SSH, and is designed
|
4
|
-
primarily for maintaining static websites. It uses a secure hash function to
|
5
|
-
determine which files don't need to be copied because the destination copy
|
6
|
-
is already identical to the source copy.
|
7
|
-
|
8
|
-
It is available as a Ruby gem.
|
9
|
-
|
10
|
-
I wrote *synqa* for two main reasons:
|
11
|
-
|
12
|
-
* I couldn't get *rsync* to work on the combination of Cygwin and my
|
13
|
-
hosting provider, and the rsync error messages were not very informative.
|
14
|
-
* It was an opportunity to learn about SSH and how to use SSH and SCP with Ruby.
|
15
|
-
|
16
|
-
== Installation
|
17
|
-
|
18
|
-
<code>gem install synqa</code>
|
19
|
-
|
20
|
-
== Dependencies of *synqa* are:
|
21
|
-
|
22
|
-
* Ruby 1.9.2
|
23
|
-
* Ruby gems *net-ssh* and *net-scp*
|
24
|
-
|
25
|
-
Optionally:
|
26
|
-
* An external SSH client. I use *plink*.
|
27
|
-
* An external SCP client. I use *pscp*.
|
28
|
-
|
29
|
-
For some sample code, see <b>examples/synga-useage.rb</b> and <b>examples/sample-rakefile</b>.
|
30
|
-
|
31
|
-
== Licence
|
32
|
-
|
33
|
-
Synqa is licensed under the GNU General Public License version 3.
|
34
|
-
|
35
|
-
== Notes and Issues
|
36
|
-
|
37
|
-
* *Synqa* has not been tested (or even designed to work) with file names
|
38
|
-
containing whitespace or non-ASCII characters. Typically this doesn't matter for
|
39
|
-
many static websites, but it will reduce the tool's usefulness as a general purpose
|
40
|
-
backup tool.
|
41
|
-
|
42
|
-
* Currently *Synqa* does not provide authentication options, on the assumption that you
|
43
|
-
will use Pageant (which automagically provides "presented" keys for specified user/host combinations).
|
1
|
+
= synqa
|
2
|
+
|
3
|
+
*Synqa* is a simple file syncing tool that works over SSH, and is designed
|
4
|
+
primarily for maintaining static websites. It uses a secure hash function to
|
5
|
+
determine which files don't need to be copied because the destination copy
|
6
|
+
is already identical to the source copy.
|
7
|
+
|
8
|
+
It is available as a Ruby gem.
|
9
|
+
|
10
|
+
I wrote *synqa* for two main reasons:
|
11
|
+
|
12
|
+
* I couldn't get *rsync* to work on the combination of Cygwin and my
|
13
|
+
hosting provider, and the rsync error messages were not very informative.
|
14
|
+
* It was an opportunity to learn about SSH and how to use SSH and SCP with Ruby.
|
15
|
+
|
16
|
+
== Installation
|
17
|
+
|
18
|
+
<code>gem install synqa</code>
|
19
|
+
|
20
|
+
== Dependencies of *synqa* are:
|
21
|
+
|
22
|
+
* Ruby 1.9.2
|
23
|
+
* Ruby gems *net-ssh* and *net-scp*
|
24
|
+
|
25
|
+
Optionally:
|
26
|
+
* An external SSH client. I use *plink*.
|
27
|
+
* An external SCP client. I use *pscp*.
|
28
|
+
|
29
|
+
For some sample code, see <b>examples/synga-useage.rb</b> and <b>examples/sample-rakefile</b>.
|
30
|
+
|
31
|
+
== Licence
|
32
|
+
|
33
|
+
Synqa is licensed under the GNU General Public License version 3.
|
34
|
+
|
35
|
+
== Notes and Issues
|
36
|
+
|
37
|
+
* *Synqa* has not been tested (or even designed to work) with file names
|
38
|
+
containing whitespace or non-ASCII characters. Typically this doesn't matter for
|
39
|
+
many static websites, but it will reduce the tool's usefulness as a general purpose
|
40
|
+
backup tool.
|
41
|
+
|
42
|
+
* Currently *Synqa* does not provide authentication options, on the assumption that you
|
43
|
+
will use Pageant (which automagically provides "presented" keys for specified user/host combinations).
|
data/Rakefile
CHANGED
@@ -1,53 +1,53 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
begin
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
10
|
-
require 'rake'
|
11
|
-
|
12
|
-
require 'jeweler'
|
13
|
-
Jeweler::Tasks.new do |gem|
|
14
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
-
gem.name = "synqa"
|
16
|
-
gem.homepage = "http://www.1729.com/software/synqa/"
|
17
|
-
gem.license = "GPLv3"
|
18
|
-
gem.summary = %Q{Sync files from a local directory to a remote directory via SSH/SCP}
|
19
|
-
gem.description = %Q{
|
20
|
-
gem.email = "http://www.1729.com/email.html"
|
21
|
-
gem.authors = ["Philip Dorrell"]
|
22
|
-
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
-
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
-
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
-
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
-
end
|
27
|
-
Jeweler::RubygemsDotOrgTasks.new
|
28
|
-
|
29
|
-
require 'rake/testtask'
|
30
|
-
Rake::TestTask.new(:test) do |test|
|
31
|
-
test.libs << 'lib' << 'test'
|
32
|
-
test.pattern = 'test/**/test_*.rb'
|
33
|
-
test.verbose = true
|
34
|
-
end
|
35
|
-
|
36
|
-
require 'rcov/rcovtask'
|
37
|
-
Rcov::RcovTask.new do |test|
|
38
|
-
test.libs << 'test'
|
39
|
-
test.pattern = 'test/**/test_*.rb'
|
40
|
-
test.verbose = true
|
41
|
-
end
|
42
|
-
|
43
|
-
task :default => :test
|
44
|
-
|
45
|
-
require 'rake/rdoctask'
|
46
|
-
Rake::RDocTask.new do |rdoc|
|
47
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
-
|
49
|
-
rdoc.rdoc_dir = 'rdoc'
|
50
|
-
rdoc.title = "synqa #{version}"
|
51
|
-
rdoc.rdoc_files.include('README*')
|
52
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
-
end
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "synqa"
|
16
|
+
gem.homepage = "http://www.1729.com/software/synqa/"
|
17
|
+
gem.license = "GPLv3"
|
18
|
+
gem.summary = %Q{Sync files from a local directory to a remote directory via SSH/SCP}
|
19
|
+
gem.description = %Q{Synqa syncs files from a local directory to a remote directory using an SSH connection. It is designed for uploading a static website. It determines if existing files need to be updated by calculating cryptographic hashes on the remote server.}
|
20
|
+
gem.email = "http://www.1729.com/email.html"
|
21
|
+
gem.authors = ["Philip Dorrell"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rake/testtask'
|
30
|
+
Rake::TestTask.new(:test) do |test|
|
31
|
+
test.libs << 'lib' << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'rcov/rcovtask'
|
37
|
+
Rcov::RcovTask.new do |test|
|
38
|
+
test.libs << 'test'
|
39
|
+
test.pattern = 'test/**/test_*.rb'
|
40
|
+
test.verbose = true
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "synqa #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/_project.el
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
|
2
|
-
(load-this-project
|
3
|
-
`( (:ruby-executable ,*ruby-1.9-executable*)
|
4
|
-
(:run-project-command (ruby-run-file ,(concat (project-base-directory) "RunMain.rb")))
|
5
|
-
(:build-function project-compile-with-command)
|
6
|
-
(:compile-command "rake")
|
7
|
-
(:ruby-args ("-I
|
8
|
-
) )
|
9
|
-
|
1
|
+
|
2
|
+
(load-this-project
|
3
|
+
`( (:ruby-executable ,*ruby-1.9-executable*)
|
4
|
+
(:run-project-command (ruby-run-file ,(concat (project-base-directory) "RunMain.rb")))
|
5
|
+
(:build-function project-compile-with-command)
|
6
|
+
(:compile-command "rake")
|
7
|
+
(:ruby-args (,(concat "-I" (project-base-directory) "lib")))
|
8
|
+
) )
|
9
|
+
|
data/examples/synqa-useage.rb
CHANGED
@@ -1,37 +1,37 @@
|
|
1
|
-
# Sample code for synqa useage -- you will need to fill in your own details
|
2
|
-
|
3
|
-
require 'synqa.rb'
|
4
|
-
require 'based'
|
5
|
-
require 'digest/sha2'
|
6
|
-
|
7
|
-
STDOUT.sync = true
|
8
|
-
|
9
|
-
include Synqa
|
10
|
-
sha256Sum = Sha256SumCommand.new() # sha256sum (with 2 characters between hash and file name)
|
11
|
-
sha256 = Sha256Command.new() # sha256 -r (with 1 space between hash and file name)
|
12
|
-
|
13
|
-
# Default is use Ruby net-ssh & net-scp gems, the following line defines external SSH/SCP applications
|
14
|
-
# plinkAndPscp = ExternalSshScp.new("plink", "pscp")
|
15
|
-
|
16
|
-
localContentLocation = LocalContentLocation.new(Based::BaseDirectory.new("c:/dev/src/project"),
|
17
|
-
Digest::SHA256,
|
18
|
-
"c:/temp/synqa/local.project.content.cache.txt")
|
19
|
-
# Default uses Ruby net-ssh & net-scp gems
|
20
|
-
remoteHost = SshContentHost.new("username@host.example.com", sha256)
|
21
|
-
|
22
|
-
# Alternative uses plink & pscp
|
23
|
-
#remoteHost = SshContentHost.new("username@host.example.com", sha256, ExternalSshScp)
|
24
|
-
|
25
|
-
|
26
|
-
# Note: the specification of plink & pscp assumes that keys are managed with Pageant, and therefore
|
27
|
-
# do not need to be specified on the command line.
|
28
|
-
|
29
|
-
remoteContentLocation = RemoteContentLocation.new(remoteHost,
|
30
|
-
"/home/username/public",
|
31
|
-
"c:/temp/synqa/remote.project.content.cache.txt")
|
32
|
-
|
33
|
-
# Note: the cache files are currently written, but not yet used to speed up the sync
|
34
|
-
|
35
|
-
syncOperation = SyncOperation.new(localContentLocation, remoteContentLocation)
|
36
|
-
|
37
|
-
syncOperation.doSync(:dryRun => true) # set to false to make it actually happen
|
1
|
+
# Sample code for synqa useage -- you will need to fill in your own details
|
2
|
+
|
3
|
+
require 'synqa.rb'
|
4
|
+
require 'based'
|
5
|
+
require 'digest/sha2'
|
6
|
+
|
7
|
+
STDOUT.sync = true
|
8
|
+
|
9
|
+
include Synqa
|
10
|
+
sha256Sum = Sha256SumCommand.new() # sha256sum (with 2 characters between hash and file name)
|
11
|
+
sha256 = Sha256Command.new() # sha256 -r (with 1 space between hash and file name)
|
12
|
+
|
13
|
+
# Default is use Ruby net-ssh & net-scp gems, the following line defines external SSH/SCP applications
|
14
|
+
# plinkAndPscp = ExternalSshScp.new("plink", "pscp")
|
15
|
+
|
16
|
+
localContentLocation = LocalContentLocation.new(Based::BaseDirectory.new("c:/dev/src/project"),
|
17
|
+
Digest::SHA256,
|
18
|
+
"c:/temp/synqa/local.project.content.cache.txt")
|
19
|
+
# Default uses Ruby net-ssh & net-scp gems
|
20
|
+
remoteHost = SshContentHost.new("username@host.example.com", sha256)
|
21
|
+
|
22
|
+
# Alternative uses plink & pscp
|
23
|
+
#remoteHost = SshContentHost.new("username@host.example.com", sha256, ExternalSshScp)
|
24
|
+
|
25
|
+
|
26
|
+
# Note: the specification of plink & pscp assumes that keys are managed with Pageant, and therefore
|
27
|
+
# do not need to be specified on the command line.
|
28
|
+
|
29
|
+
remoteContentLocation = RemoteContentLocation.new(remoteHost,
|
30
|
+
"/home/username/public",
|
31
|
+
"c:/temp/synqa/remote.project.content.cache.txt")
|
32
|
+
|
33
|
+
# Note: the cache files are currently written, but not yet used to speed up the sync
|
34
|
+
|
35
|
+
syncOperation = SyncOperation.new(localContentLocation, remoteContentLocation)
|
36
|
+
|
37
|
+
syncOperation.doSync(:dryRun => true) # set to false to make it actually happen
|
data/lib/based.rb
CHANGED
@@ -1,177 +1,177 @@
|
|
1
|
-
# The Based module supports the concept of a "based" directory. Typically in modern software development,
|
2
|
-
# a project is represented by a set of sub-directories and files within a base directory. The exact location
|
3
|
-
# of the base directory is not so important (i.e. it's wherever you checked it out of source control). For a given
|
4
|
-
# file or sub-directory, one is often more interested in the path relative to the base directory, rather than
|
5
|
-
# the absolute path. (But you still need the full path when performing an actual file operation on the file
|
6
|
-
# or directory.)
|
7
|
-
# Also, there might be files and directories that you wish to ignore. Based supports simple functional includes and
|
8
|
-
# excludes. (A bit less succint than include/exclude globs, but probably more flexible.)
|
9
|
-
|
10
|
-
module Based
|
11
|
-
|
12
|
-
# A base class for directories: i.e. either the base directory itself, or a sub-directory
|
13
|
-
class Directory
|
14
|
-
# The base directory (object)
|
15
|
-
attr_reader :base
|
16
|
-
|
17
|
-
# The path of this directory relative to the base directory (includes a following "/" if non-empty)
|
18
|
-
attr_reader :relativePath
|
19
|
-
|
20
|
-
# The elements of the relative path as an array
|
21
|
-
attr_reader :pathElements
|
22
|
-
|
23
|
-
# The immediate name of the directory (nil for the base directory)
|
24
|
-
attr_reader :name
|
25
|
-
|
26
|
-
# The parent directory (nil for the base directory)
|
27
|
-
attr_reader :parent
|
28
|
-
|
29
|
-
# The full path of the file
|
30
|
-
attr_reader :fullPath
|
31
|
-
|
32
|
-
# initialise with un-initialised entries
|
33
|
-
def initialize
|
34
|
-
@entries = nil
|
35
|
-
end
|
36
|
-
|
37
|
-
# get the "entries", i.e. list of files and directories immediately contained in this directory, and cache them.
|
38
|
-
# Note that dirExclude, fileInclude and fileExclude functions in the base directory object
|
39
|
-
# may be applied to filter out some entries.
|
40
|
-
def getEntries
|
41
|
-
if @entries == nil
|
42
|
-
@entries = Dir.entries(fullPath)
|
43
|
-
@dirs = []
|
44
|
-
@files = []
|
45
|
-
for entry in @entries
|
46
|
-
if entry != "." and entry != ".."
|
47
|
-
fullEntryPath = fullPath + entry
|
48
|
-
if ::File.directory?(fullEntryPath)
|
49
|
-
subDirectory = SubDirectory.new(entry, self)
|
50
|
-
if @base.dirExclude == nil or not @base.dirExclude.call(subDirectory)
|
51
|
-
@dirs << subDirectory
|
52
|
-
end
|
53
|
-
elsif ::File.file?(fullEntryPath)
|
54
|
-
file = File.new(entry, self)
|
55
|
-
if @base.fileInclude == nil or @base.fileInclude.call(file)
|
56
|
-
if @base.fileExclude == nil or not @base.fileExclude.call(file)
|
57
|
-
@files << file
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
@dirs.sort_by! {|dir| dir.name}
|
64
|
-
@files.sort_by! {|file| file.name}
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# Get list of files immediately contained in this directory
|
69
|
-
def files
|
70
|
-
getEntries()
|
71
|
-
return @files
|
72
|
-
end
|
73
|
-
|
74
|
-
# Get list of directories immediately contained in this directory
|
75
|
-
def dirs
|
76
|
-
getEntries()
|
77
|
-
return @dirs
|
78
|
-
end
|
79
|
-
|
80
|
-
# Get a list of all the sub-directories of this directory (with parents preceding children)
|
81
|
-
def subDirs
|
82
|
-
result = []
|
83
|
-
for dir in dirs
|
84
|
-
result << dir
|
85
|
-
result += dir.subDirs
|
86
|
-
end
|
87
|
-
return result
|
88
|
-
end
|
89
|
-
|
90
|
-
# Get a list of all files contained within this directory
|
91
|
-
def allFiles
|
92
|
-
result = files
|
93
|
-
for subDir in subDirs
|
94
|
-
result += subDir.files
|
95
|
-
end
|
96
|
-
return result
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# An object representing a sub-directory (i.e. not the base directory itself)
|
101
|
-
class SubDirectory<Directory
|
102
|
-
|
103
|
-
# Construct directory object from parent directory object and this directory's name
|
104
|
-
def initialize(name, parent)
|
105
|
-
super()
|
106
|
-
@name = name
|
107
|
-
@parent = parent
|
108
|
-
@base = @parent.base
|
109
|
-
@relativePath = @parent.relativePath + @name + "/"
|
110
|
-
@pathElements = @parent.pathElements + [name]
|
111
|
-
@fullPath = @parent.fullPath + @name + "/"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# An object representing the base directory
|
116
|
-
class BaseDirectory<Directory
|
117
|
-
|
118
|
-
# Function to decide if files should be included (if nil, assume all included). Subject
|
119
|
-
# to exclusion by fileExclude
|
120
|
-
attr_reader :fileInclude
|
121
|
-
|
122
|
-
# Function to decide if files should be excluded
|
123
|
-
attr_reader :fileExclude
|
124
|
-
|
125
|
-
# Function to decide if sub-directories should be excluded (if a directory is excluded, so
|
126
|
-
# are all it's sub-directories and files contained within)
|
127
|
-
attr_reader :dirExclude
|
128
|
-
|
129
|
-
# Initialise from absolute file path. Options include :dirExclude, :fileInclude and :fileExclude
|
130
|
-
def initialize(path, options = {})
|
131
|
-
super()
|
132
|
-
@name = nil
|
133
|
-
@parent = nil
|
134
|
-
@base = self
|
135
|
-
@relativePath = ""
|
136
|
-
@pathElements = []
|
137
|
-
@fullPath = path.end_with?("/") ? path : path + "/"
|
138
|
-
@dirExclude = options.fetch(:dirExclude, nil)
|
139
|
-
@fileInclude = options.fetch(:fileInclude, nil)
|
140
|
-
@fileExclude = options.fetch(:fileExclude, nil)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# An object representing a file within the base directory
|
145
|
-
class File
|
146
|
-
# immediate name of file
|
147
|
-
attr_reader :name
|
148
|
-
|
149
|
-
# parent, i.e. containing directory
|
150
|
-
attr_reader :parent
|
151
|
-
|
152
|
-
# the base directory
|
153
|
-
attr_reader :base
|
154
|
-
|
155
|
-
# path of this file relative to base directory
|
156
|
-
attr_reader :relativePath
|
157
|
-
|
158
|
-
# elements of file path (including the name) as an array
|
159
|
-
attr_reader :pathElements
|
160
|
-
|
161
|
-
# full absolute path name of file
|
162
|
-
attr_reader :fullPath
|
163
|
-
|
164
|
-
# initialise from name and containing directory
|
165
|
-
def initialize(name, parent)
|
166
|
-
super()
|
167
|
-
@name = name
|
168
|
-
@parent = parent
|
169
|
-
@base = @parent.base
|
170
|
-
@relativePath = @parent.relativePath + @name
|
171
|
-
@pathElements = @parent.pathElements + [name]
|
172
|
-
@fullPath = @parent.fullPath + @name
|
173
|
-
end
|
174
|
-
|
175
|
-
end
|
176
|
-
|
177
|
-
end
|
1
|
+
# The Based module supports the concept of a "based" directory. Typically in modern software development,
|
2
|
+
# a project is represented by a set of sub-directories and files within a base directory. The exact location
|
3
|
+
# of the base directory is not so important (i.e. it's wherever you checked it out of source control). For a given
|
4
|
+
# file or sub-directory, one is often more interested in the path relative to the base directory, rather than
|
5
|
+
# the absolute path. (But you still need the full path when performing an actual file operation on the file
|
6
|
+
# or directory.)
|
7
|
+
# Also, there might be files and directories that you wish to ignore. Based supports simple functional includes and
|
8
|
+
# excludes. (A bit less succint than include/exclude globs, but probably more flexible.)
|
9
|
+
|
10
|
+
module Based
|
11
|
+
|
12
|
+
# A base class for directories: i.e. either the base directory itself, or a sub-directory
|
13
|
+
class Directory
|
14
|
+
# The base directory (object)
|
15
|
+
attr_reader :base
|
16
|
+
|
17
|
+
# The path of this directory relative to the base directory (includes a following "/" if non-empty)
|
18
|
+
attr_reader :relativePath
|
19
|
+
|
20
|
+
# The elements of the relative path as an array
|
21
|
+
attr_reader :pathElements
|
22
|
+
|
23
|
+
# The immediate name of the directory (nil for the base directory)
|
24
|
+
attr_reader :name
|
25
|
+
|
26
|
+
# The parent directory (nil for the base directory)
|
27
|
+
attr_reader :parent
|
28
|
+
|
29
|
+
# The full path of the file
|
30
|
+
attr_reader :fullPath
|
31
|
+
|
32
|
+
# initialise with un-initialised entries
|
33
|
+
def initialize
|
34
|
+
@entries = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# get the "entries", i.e. list of files and directories immediately contained in this directory, and cache them.
|
38
|
+
# Note that dirExclude, fileInclude and fileExclude functions in the base directory object
|
39
|
+
# may be applied to filter out some entries.
|
40
|
+
def getEntries
|
41
|
+
if @entries == nil
|
42
|
+
@entries = Dir.entries(fullPath)
|
43
|
+
@dirs = []
|
44
|
+
@files = []
|
45
|
+
for entry in @entries
|
46
|
+
if entry != "." and entry != ".."
|
47
|
+
fullEntryPath = fullPath + entry
|
48
|
+
if ::File.directory?(fullEntryPath)
|
49
|
+
subDirectory = SubDirectory.new(entry, self)
|
50
|
+
if @base.dirExclude == nil or not @base.dirExclude.call(subDirectory)
|
51
|
+
@dirs << subDirectory
|
52
|
+
end
|
53
|
+
elsif ::File.file?(fullEntryPath)
|
54
|
+
file = File.new(entry, self)
|
55
|
+
if @base.fileInclude == nil or @base.fileInclude.call(file)
|
56
|
+
if @base.fileExclude == nil or not @base.fileExclude.call(file)
|
57
|
+
@files << file
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@dirs.sort_by! {|dir| dir.name}
|
64
|
+
@files.sort_by! {|file| file.name}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get list of files immediately contained in this directory
|
69
|
+
def files
|
70
|
+
getEntries()
|
71
|
+
return @files
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get list of directories immediately contained in this directory
|
75
|
+
def dirs
|
76
|
+
getEntries()
|
77
|
+
return @dirs
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get a list of all the sub-directories of this directory (with parents preceding children)
|
81
|
+
def subDirs
|
82
|
+
result = []
|
83
|
+
for dir in dirs
|
84
|
+
result << dir
|
85
|
+
result += dir.subDirs
|
86
|
+
end
|
87
|
+
return result
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get a list of all files contained within this directory
|
91
|
+
def allFiles
|
92
|
+
result = files
|
93
|
+
for subDir in subDirs
|
94
|
+
result += subDir.files
|
95
|
+
end
|
96
|
+
return result
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# An object representing a sub-directory (i.e. not the base directory itself)
|
101
|
+
class SubDirectory<Directory
|
102
|
+
|
103
|
+
# Construct directory object from parent directory object and this directory's name
|
104
|
+
def initialize(name, parent)
|
105
|
+
super()
|
106
|
+
@name = name
|
107
|
+
@parent = parent
|
108
|
+
@base = @parent.base
|
109
|
+
@relativePath = @parent.relativePath + @name + "/"
|
110
|
+
@pathElements = @parent.pathElements + [name]
|
111
|
+
@fullPath = @parent.fullPath + @name + "/"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# An object representing the base directory
|
116
|
+
class BaseDirectory<Directory
|
117
|
+
|
118
|
+
# Function to decide if files should be included (if nil, assume all included). Subject
|
119
|
+
# to exclusion by fileExclude
|
120
|
+
attr_reader :fileInclude
|
121
|
+
|
122
|
+
# Function to decide if files should be excluded
|
123
|
+
attr_reader :fileExclude
|
124
|
+
|
125
|
+
# Function to decide if sub-directories should be excluded (if a directory is excluded, so
|
126
|
+
# are all it's sub-directories and files contained within)
|
127
|
+
attr_reader :dirExclude
|
128
|
+
|
129
|
+
# Initialise from absolute file path. Options include :dirExclude, :fileInclude and :fileExclude
|
130
|
+
def initialize(path, options = {})
|
131
|
+
super()
|
132
|
+
@name = nil
|
133
|
+
@parent = nil
|
134
|
+
@base = self
|
135
|
+
@relativePath = ""
|
136
|
+
@pathElements = []
|
137
|
+
@fullPath = path.end_with?("/") ? path : path + "/"
|
138
|
+
@dirExclude = options.fetch(:dirExclude, nil)
|
139
|
+
@fileInclude = options.fetch(:fileInclude, nil)
|
140
|
+
@fileExclude = options.fetch(:fileExclude, nil)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# An object representing a file within the base directory
|
145
|
+
class File
|
146
|
+
# immediate name of file
|
147
|
+
attr_reader :name
|
148
|
+
|
149
|
+
# parent, i.e. containing directory
|
150
|
+
attr_reader :parent
|
151
|
+
|
152
|
+
# the base directory
|
153
|
+
attr_reader :base
|
154
|
+
|
155
|
+
# path of this file relative to base directory
|
156
|
+
attr_reader :relativePath
|
157
|
+
|
158
|
+
# elements of file path (including the name) as an array
|
159
|
+
attr_reader :pathElements
|
160
|
+
|
161
|
+
# full absolute path name of file
|
162
|
+
attr_reader :fullPath
|
163
|
+
|
164
|
+
# initialise from name and containing directory
|
165
|
+
def initialize(name, parent)
|
166
|
+
super()
|
167
|
+
@name = name
|
168
|
+
@parent = parent
|
169
|
+
@base = @parent.base
|
170
|
+
@relativePath = @parent.relativePath + @name
|
171
|
+
@pathElements = @parent.pathElements + [name]
|
172
|
+
@fullPath = @parent.fullPath + @name
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|