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 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.5.2"
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
@@ -2,8 +2,8 @@ GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
4
  git (1.2.5)
5
- jeweler (1.5.2)
6
- bundler (~> 1.0.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.5.2)
22
+ jeweler (~> 1.6.2)
22
23
  net-scp (>= 1.0)
23
24
  net-ssh (>= 2.0)
24
25
  rcov
@@ -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{Sync files from a local directory to a remote directory via SSH/SCP}
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.2.0
1
+ 0.3.0
@@ -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
+
@@ -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
@@ -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