synqa 0.2.0 → 0.3.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.
- 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
|