and_feathers 0.0.1 → 1.0.0.pre
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -1
- data/Gemfile +2 -0
- data/README.md +56 -10
- data/and_feathers.gemspec +2 -1
- data/lib/and_feathers/archive.rb +99 -0
- data/lib/and_feathers/directory.rb +155 -0
- data/lib/and_feathers/file.rb +66 -0
- data/lib/and_feathers/sugar.rb +105 -0
- data/lib/and_feathers/version.rb +6 -1
- data/lib/and_feathers.rb +58 -19
- data/spec/end_to_end/and_feathers_spec.rb +162 -0
- data/spec/fixtures/archiveme/README.md +1 -0
- data/spec/fixtures/archiveme/lib/archiveme.rb +2 -0
- data/spec/unit/directory_spec.rb +103 -0
- metadata +19 -15
- data/lib/and_feathers/tarball/contains_directories.rb +0 -24
- data/lib/and_feathers/tarball/contains_files.rb +0 -27
- data/lib/and_feathers/tarball/directory.rb +0 -66
- data/lib/and_feathers/tarball/file.rb +0 -60
- data/lib/and_feathers/tarball.rb +0 -75
- data/spec/end_to_end_spec.rb +0 -106
- data/spec/support/in_memory_gzipped_tarball.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8677ebc7f56d9f906f50385f52023ac81ba58bd
|
4
|
+
data.tar.gz: c480d1890de2bc3d78f2d7da5daf317300113be0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9fb0a23273a125b5a519d11c3f8a5a00cde68e7a33afbf0c5159c8d2b10954b159c42fc9de5212f5a8d4ff97d0a1fec8d463910f1960f9fd29503465b6614b3
|
7
|
+
data.tar.gz: 9e541a6708157de758811e543cbc6bef2cdd25dea3dad6aac1c0243f0f288b62834eb702d1672633ef7788a53718826da671c09753080422a9dc803675fd4a43
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,14 +1,10 @@
|
|
1
1
|
# AndFeathers
|
2
2
|
|
3
|
-
Declaratively build in-memory
|
3
|
+
Declaratively and iteratively build in-memory archive structures.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
$ gem install and_feathers
|
10
|
-
|
11
|
-
Or, if you're using Bundler, add this line to your application's Gemfile:
|
7
|
+
Add this line to your application's Gemfile:
|
12
8
|
|
13
9
|
gem 'and_feathers'
|
14
10
|
|
@@ -16,15 +12,28 @@ And then execute:
|
|
16
12
|
|
17
13
|
$ bundle
|
18
14
|
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install and_feathers
|
18
|
+
|
19
19
|
## Usage
|
20
20
|
|
21
|
-
|
21
|
+
The examples below focus on specifying an archive's structure using `and_feathers`. See:
|
22
|
+
|
23
|
+
* [`and_feathers-gzipped_tarball`](https://github.com/bcobb/and_feathers-gzipped_tarball) for notes on writing a `.tgz` file to disk
|
24
|
+
* [`and_feathers-zip`](https://github.com/bcobb/and_feathers-zip) for notes on writing a `.zip` file to disk
|
25
|
+
|
26
|
+
Once you're "inside" `and_feathers`, either because you've called `AndFeathers.build` or `AndFeathers.from_path`, the two main methods you'll call on block parameters are `file` and `dir`. These, as you might suspect, create file and directory entries, respectively.
|
27
|
+
|
28
|
+
The examples below show how you might use these two methods to build up directory structures.
|
29
|
+
|
30
|
+
### Specify each directory and file individually
|
22
31
|
|
23
32
|
```ruby
|
24
33
|
require 'and_feathers'
|
25
34
|
require 'json'
|
26
35
|
|
27
|
-
|
36
|
+
archive = AndFeathers.build('redis') do |redis|
|
28
37
|
redis.file('README.md') { "README content" }
|
29
38
|
redis.file('metadata.json') { JSON.dump({}) }
|
30
39
|
redis.file('metadata.rb') { "# metadata.rb content" }
|
@@ -32,13 +41,50 @@ tarball = AndFeathers.build('redis') do |redis|
|
|
32
41
|
attributes.file('default.rb') { '# default.rb content' }
|
33
42
|
end
|
34
43
|
redis.dir('recipes') do |recipes|
|
35
|
-
|
44
|
+
recipes.file('default.rb') { '# default.rb content' }
|
36
45
|
end
|
37
46
|
redis.dir('templates') do |templates|
|
38
47
|
templates.dir('default')
|
39
48
|
end
|
40
49
|
end
|
50
|
+
```
|
51
|
+
|
52
|
+
### Specify directories and files by their paths
|
41
53
|
|
42
|
-
|
54
|
+
```ruby
|
55
|
+
require 'and_feathers'
|
56
|
+
|
57
|
+
archive = AndFeathers.build('rails_app') do |app|
|
58
|
+
app.file('README.md') { "README content" }
|
59
|
+
app.file('config/routes.rb') do
|
60
|
+
"root to: 'public#home'"
|
61
|
+
end
|
62
|
+
app.dir('app/controllers') do |controllers|
|
63
|
+
controllers.file('application_controller.rb') do
|
64
|
+
"class ApplicationController < ActionController:Base\nend"
|
65
|
+
end
|
66
|
+
controllers.file('public_controller.rb') do
|
67
|
+
"class PublicController < ActionController:Base\nend"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
app.file('app/views/public/home.html.erb')
|
71
|
+
end
|
43
72
|
```
|
44
73
|
|
74
|
+
### Load an existing directory as an Archive
|
75
|
+
|
76
|
+
In the example below, we load the fixture directory at [`spec/fixtures/archiveme`](/spec/fixtures/archiveme), and then use `and_feathers` to perform surgery on the in-memory archive. In particular, we add a `test` directory and file to its archive, and update its `lib` directory a couple of times.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
require 'and_feathers'
|
80
|
+
|
81
|
+
archive = AndFeathers.from_path('spec/fixtures/archiveme')
|
82
|
+
archive.file('test/basic_test.rb') { '# TODO: tests' }
|
83
|
+
archive.file('lib/archiveme/version.rb') do
|
84
|
+
"module Archiveme\n VERSION = '1.0.0'\nend"
|
85
|
+
end
|
86
|
+
archive.file('lib/archiveme.rb') do
|
87
|
+
# The Archiveme fixture is a class, but we'll change it to a module
|
88
|
+
"module Archiveme\nend"
|
89
|
+
end
|
90
|
+
```
|
data/and_feathers.gemspec
CHANGED
@@ -8,7 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = AndFeathers::VERSION
|
9
9
|
spec.authors = ["Brian Cobb"]
|
10
10
|
spec.email = ["bcobb@uwalumni.com"]
|
11
|
-
spec.
|
11
|
+
spec.description = %q{Declaratively and iteratively build archive structures which easily serialize to on-disk formats such as zip and tgz.}
|
12
|
+
spec.summary = %q{In-memory archive structures}
|
12
13
|
spec.homepage = "http://github.com/bcobb/and_feathers"
|
13
14
|
spec.license = "MIT"
|
14
15
|
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'and_feathers/file'
|
2
|
+
require 'and_feathers/directory'
|
3
|
+
require 'and_feathers/sugar'
|
4
|
+
|
5
|
+
module AndFeathers
|
6
|
+
#
|
7
|
+
# The parent class of a given directory tree. It knows whether or not the
|
8
|
+
# archive's contents should be extracted into its own directory or into its
|
9
|
+
# containing directory.
|
10
|
+
#
|
11
|
+
# An +Archive+ exposes the same sugary interface exposed by a +Directory+,
|
12
|
+
# but it is implemented so that adding files and directories directly to the
|
13
|
+
# +Archive+ is not destructive. In fact, whenever a file or directory is
|
14
|
+
# added to an +Archive+, the +Archive+ creates a new top-level directory, and
|
15
|
+
# delegates the change to it. That way, when it's time to enumerate the
|
16
|
+
# entries in the +Archive+, we can take the union of all of these top-level
|
17
|
+
# directories and enumerate _its_ entries. Thus, the only possibility for
|
18
|
+
# loss is if two changes modify the same file, in which case we take the
|
19
|
+
# latest file to be the authoritative file.
|
20
|
+
#
|
21
|
+
class Archive
|
22
|
+
include Sugar
|
23
|
+
include Enumerable
|
24
|
+
|
25
|
+
#
|
26
|
+
# Creates a new +Archive+
|
27
|
+
#
|
28
|
+
# @param extract_to [String] the path under which the +Archive+ should be
|
29
|
+
# extracted
|
30
|
+
# @param extraction_mode [Fixnum] the mode of the +Archive+'s base directory
|
31
|
+
#
|
32
|
+
def initialize(extract_to = '.', extraction_mode = 16877)
|
33
|
+
@initial_version = Directory.new(extract_to, extraction_mode)
|
34
|
+
@versions = [@initial_version]
|
35
|
+
@extract_to = extract_to
|
36
|
+
@extraction_mode = extraction_mode
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Adds a +Directory+ to the top level of the +Archive+
|
41
|
+
#
|
42
|
+
# @param directory [Directory]
|
43
|
+
#
|
44
|
+
def add_directory(directory)
|
45
|
+
@versions << Directory.new(@extract_to, @extraction_mode).tap do |parent|
|
46
|
+
parent.add_directory(directory)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Adds a +File+ to the top level of the +Archive+
|
52
|
+
#
|
53
|
+
# @param file [File]
|
54
|
+
#
|
55
|
+
def add_file(file)
|
56
|
+
@initial_version.file(file.name, file.mode, &file.content)
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Iterates depth-first through the +Archive+'s entries
|
61
|
+
#
|
62
|
+
# @yieldparam entry [Directory, File]
|
63
|
+
#
|
64
|
+
def each(&block)
|
65
|
+
@versions.reduce(&:|).each(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Returns this +Archive+ as a package of the given +package_type+
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# require 'and_feathers/gzipped_tarball'
|
73
|
+
#
|
74
|
+
# format = AndFeathers::GzippedTarball
|
75
|
+
# AndFeathers::Archive.new('test', 16877).to_io(format)
|
76
|
+
#
|
77
|
+
# @see https://github.com/bcobb/and_feathers-gzipped_tarball
|
78
|
+
# @see https://github.com/bcobb/and_feathers-zip
|
79
|
+
#
|
80
|
+
# @param package_type [.open,#add_file,#add_directory]
|
81
|
+
#
|
82
|
+
# @return [StringIO]
|
83
|
+
#
|
84
|
+
def to_io(package_type)
|
85
|
+
package_type.open do |package|
|
86
|
+
package.add_directory(@initial_version)
|
87
|
+
|
88
|
+
each do |child|
|
89
|
+
case child
|
90
|
+
when File
|
91
|
+
package.add_file(child)
|
92
|
+
when Directory
|
93
|
+
package.add_directory(child)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'and_feathers/sugar'
|
2
|
+
|
3
|
+
module AndFeathers
|
4
|
+
#
|
5
|
+
# Represents a Directory
|
6
|
+
#
|
7
|
+
class Directory
|
8
|
+
include Sugar
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
attr_reader :name, :mode
|
12
|
+
attr_writer :parent
|
13
|
+
|
14
|
+
#
|
15
|
+
# @!attribute [r] name
|
16
|
+
# @return [String] the directory name
|
17
|
+
#
|
18
|
+
# @!attribute [r] mode
|
19
|
+
# @return [Fixnum] the directory mode
|
20
|
+
#
|
21
|
+
# @!attribute [rw] parent
|
22
|
+
# @return [Directory] the directory's parent
|
23
|
+
#
|
24
|
+
|
25
|
+
#
|
26
|
+
# Creates a new +Directory+
|
27
|
+
#
|
28
|
+
# @param name [String] the directory name
|
29
|
+
# @param mode [Fixnum] the directory mode
|
30
|
+
#
|
31
|
+
def initialize(name = '.', mode = 16877)
|
32
|
+
@name = name
|
33
|
+
@mode = mode
|
34
|
+
@parent = nil
|
35
|
+
@files = {}
|
36
|
+
@directories = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# This +Directory+'s path
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
#
|
44
|
+
def path
|
45
|
+
if @parent
|
46
|
+
::File.join(@parent.path, name)
|
47
|
+
else
|
48
|
+
if name != '.'
|
49
|
+
::File.join('.', name)
|
50
|
+
else
|
51
|
+
name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Determines this +Directory+'s path relative to the given path.
|
58
|
+
#
|
59
|
+
# @return [String]
|
60
|
+
#
|
61
|
+
def path_from(relative_path)
|
62
|
+
path.sub(/^#{Regexp.escape(relative_path)}\/?/, '')
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Computes the union of this +Directory+ with another +Directory+. If the
|
67
|
+
# two directories have a file path in common, the file in the +other+
|
68
|
+
# +Directory+ takes precedence. If the two directories have a sub-directory
|
69
|
+
# path in common, the union's sub-directory path will be the union of those
|
70
|
+
# two sub-directories.
|
71
|
+
#
|
72
|
+
# @raise [ArgumentError] if the +other+ parameter is not a +Directory+
|
73
|
+
#
|
74
|
+
# @param other [Directory]
|
75
|
+
#
|
76
|
+
# @return [Directory]
|
77
|
+
#
|
78
|
+
def |(other)
|
79
|
+
if !other.is_a?(Directory)
|
80
|
+
raise ArgumentError, "#{other} is not a Directory"
|
81
|
+
end
|
82
|
+
|
83
|
+
self.dup.tap do |directory|
|
84
|
+
other.files.each do |file|
|
85
|
+
directory.add_file(file.dup)
|
86
|
+
end
|
87
|
+
|
88
|
+
other.directories.each do |new_directory|
|
89
|
+
existing_directory = @directories[new_directory.name]
|
90
|
+
|
91
|
+
if existing_directory.nil?
|
92
|
+
directory.add_directory(new_directory.dup)
|
93
|
+
else
|
94
|
+
directory.add_directory(new_directory.dup | existing_directory.dup)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# The +File+ entries which exist in this +Directory+
|
102
|
+
#
|
103
|
+
# @return [Array<File>]
|
104
|
+
#
|
105
|
+
def files
|
106
|
+
@files.values
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# The +Directory+ entries which exist in this +Directory+
|
111
|
+
#
|
112
|
+
# @return [Array<Directory>]
|
113
|
+
#
|
114
|
+
def directories
|
115
|
+
@directories.values
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Iterates through this +Directory+'s children depth-first
|
120
|
+
#
|
121
|
+
# @yieldparam child [File, Directory]
|
122
|
+
#
|
123
|
+
def each(&block)
|
124
|
+
files.each(&block)
|
125
|
+
|
126
|
+
directories.each do |subdirectory|
|
127
|
+
block.call(subdirectory)
|
128
|
+
|
129
|
+
subdirectory.each(&block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Sets the given +directory+'s parent to this +Directory+, and adds it as a
|
135
|
+
# child.
|
136
|
+
#
|
137
|
+
# @param directory [Directory]
|
138
|
+
#
|
139
|
+
def add_directory(directory)
|
140
|
+
@directories[directory.name] = directory
|
141
|
+
directory.parent = self
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Sets the given +file+'s parent to this +Directory+, and adds it as a
|
146
|
+
# child.
|
147
|
+
#
|
148
|
+
# @param file [File]
|
149
|
+
#
|
150
|
+
def add_file(file)
|
151
|
+
@files[file.name] = file
|
152
|
+
file.parent = self
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module AndFeathers
|
2
|
+
#
|
3
|
+
# Represents a File inside the archive
|
4
|
+
#
|
5
|
+
class File
|
6
|
+
attr_reader :name, :mode, :content
|
7
|
+
attr_writer :parent
|
8
|
+
|
9
|
+
#
|
10
|
+
# @!attribute [r] name
|
11
|
+
# @return [String] the file's name
|
12
|
+
#
|
13
|
+
# @!attribute [r] mode
|
14
|
+
# @return [Fixnum] the file's mode
|
15
|
+
#
|
16
|
+
# @!attribute [r] content
|
17
|
+
# @return [Fixnum] a block which returns the file's content
|
18
|
+
#
|
19
|
+
# @!attribute [rw] parent
|
20
|
+
# @return [Directory] the file's parent
|
21
|
+
#
|
22
|
+
|
23
|
+
#
|
24
|
+
# Creates a new +File+
|
25
|
+
#
|
26
|
+
# @param name [String] the file name
|
27
|
+
# @param mode [Fixnum] the file mode
|
28
|
+
# @param content [Proc] a block which returns the file contents
|
29
|
+
#
|
30
|
+
def initialize(name, mode, content)
|
31
|
+
@name = name
|
32
|
+
@mode = mode
|
33
|
+
@content = content
|
34
|
+
@parent = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# This +File+'s path
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
#
|
42
|
+
def path
|
43
|
+
if @parent
|
44
|
+
::File.join(@parent.path, name)
|
45
|
+
else
|
46
|
+
::File.join('.', name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Determines this +File+'s path relative to the given path.
|
52
|
+
#
|
53
|
+
# @return [String]
|
54
|
+
#
|
55
|
+
def path_from(relative_path)
|
56
|
+
path.sub(/^#{Regexp.escape(relative_path)}\/?/, '')
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# This +File+'s contents
|
61
|
+
#
|
62
|
+
def read
|
63
|
+
@content.call
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module AndFeathers
|
2
|
+
#
|
3
|
+
# A module which provides convenience methods for defining a directory/file
|
4
|
+
# structure
|
5
|
+
#
|
6
|
+
module Sugar
|
7
|
+
#
|
8
|
+
# Add a +Directory+ named +name+ to this entity's list of children. The
|
9
|
+
# +name+ may simply be the name of the directory, or may be a path to the
|
10
|
+
# directory.
|
11
|
+
#
|
12
|
+
# In the case of the latter, +dir+ will create the +Directory+ tree
|
13
|
+
# specified by the path. The block parameter yielded in this case will be
|
14
|
+
# the innermost directory.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# archive = Directory.new
|
18
|
+
# archive.dir('app') do |app|
|
19
|
+
# app.name == 'app'
|
20
|
+
# app.path == './app'
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# archive.dir('app/controllers/concerns') do |concerns|
|
25
|
+
# concerns.name == 'concerns'
|
26
|
+
# concerns.path == './app/controllers/concerns'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @param name [String] the directory name
|
30
|
+
# @param mode [Fixnum] the directory mode
|
31
|
+
#
|
32
|
+
# @yieldparam directory [AndFeathers::Directory] the newly-created
|
33
|
+
# +Directory+
|
34
|
+
#
|
35
|
+
def dir(name, mode = 16877, &block)
|
36
|
+
name_parts = name.split(::File::SEPARATOR)
|
37
|
+
|
38
|
+
innermost_child_name = name_parts.pop
|
39
|
+
|
40
|
+
if name_parts.empty?
|
41
|
+
Directory.new(name, mode).tap do |directory|
|
42
|
+
add_directory(directory)
|
43
|
+
|
44
|
+
block.call(directory) if block
|
45
|
+
end
|
46
|
+
else
|
47
|
+
innermost_parent = name_parts.reduce(self) do |parent, child_name|
|
48
|
+
parent.dir(child_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
innermost_parent.dir(innermost_child_name, &block)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# The default file content block, which returns an empty string
|
57
|
+
#
|
58
|
+
NO_CONTENT = Proc.new { "" }
|
59
|
+
|
60
|
+
#
|
61
|
+
# Add a +File+ named +name+ to this entity's list of children. The +name+
|
62
|
+
# may simply be the name of the file or may be a path to the file.
|
63
|
+
#
|
64
|
+
# In the case of the latter, +file+ will create the +Directory+ tree
|
65
|
+
# which contains the +File+ specified by the path.
|
66
|
+
#
|
67
|
+
# Either way, the +File+'s contents will be set to the result of the
|
68
|
+
# given block, or to a blank string if no block is given
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# archive = Directory.new
|
72
|
+
# archive.file('README') do
|
73
|
+
# "Cool"
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# archive = Directory.new
|
78
|
+
# archive.file('app/models/user.rb') do
|
79
|
+
# "class User < ActiveRecord::Base\nend"
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# @param name [String] the file name
|
83
|
+
# @param mode [Fixnum] the file mode
|
84
|
+
#
|
85
|
+
# @yieldreturn [String] the file contents
|
86
|
+
#
|
87
|
+
def file(name, mode = 33188, &content)
|
88
|
+
content ||= NO_CONTENT
|
89
|
+
|
90
|
+
name_parts = name.split(::File::SEPARATOR)
|
91
|
+
|
92
|
+
file_name = name_parts.pop
|
93
|
+
|
94
|
+
if name_parts.empty?
|
95
|
+
File.new(name, mode, content).tap do |file|
|
96
|
+
add_file(file)
|
97
|
+
end
|
98
|
+
else
|
99
|
+
dir(name_parts.join(::File::SEPARATOR)) do |parent|
|
100
|
+
parent.file(file_name, mode, &content)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/and_feathers/version.rb
CHANGED
data/lib/and_feathers.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
require 'and_feathers/
|
2
|
-
require 'and_feathers/tarball/directory'
|
3
|
-
require 'and_feathers/tarball'
|
1
|
+
require 'and_feathers/archive'
|
4
2
|
require 'and_feathers/version'
|
5
3
|
|
6
4
|
#
|
@@ -8,29 +6,70 @@ require 'and_feathers/version'
|
|
8
6
|
#
|
9
7
|
module AndFeathers
|
10
8
|
#
|
11
|
-
# Builds a new
|
9
|
+
# Builds a new archive. If +base+ is not given, the archives's contents
|
12
10
|
# would be extracted to intermingle with whichever directory contains the
|
13
|
-
#
|
14
|
-
# directory with that name.
|
15
|
-
# call wrap the tarball's contents
|
11
|
+
# archive. If +base+ is given, the archive's contents will live inside a
|
12
|
+
# directory with that name.
|
16
13
|
#
|
17
|
-
# @param
|
14
|
+
# @param extract_to [String] name of the base directory containing the archive's
|
18
15
|
# contents
|
19
|
-
# @param
|
16
|
+
# @param extraction_mode [Fixnum] the mode of the base directory
|
20
17
|
#
|
21
|
-
# @yieldparam
|
18
|
+
# @yieldparam archive [Archive]
|
22
19
|
#
|
23
|
-
def self.build(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
def self.build(extract_to = nil, extraction_mode = 16877, &block)
|
21
|
+
extract_to ||= '.'
|
22
|
+
|
23
|
+
Archive.new(extract_to, extraction_mode).tap do |archive|
|
24
|
+
block.call(archive)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Builds a new archive from the directory at the given +path+. The
|
30
|
+
# innermost directory is taken to be the parent folder of the archive's
|
31
|
+
# contents.
|
32
|
+
#
|
33
|
+
# @param path [String] path to the directory to archive
|
34
|
+
#
|
35
|
+
# @yieldparam archive [Archive] the loaded archive
|
36
|
+
#
|
37
|
+
def self.from_path(path, &block)
|
38
|
+
if !::File.exists?(path)
|
39
|
+
raise ArgumentError, "#{path} does not exist"
|
40
|
+
end
|
41
|
+
|
42
|
+
directories, files = ::Dir[::File.join(path, '**/*')].partition do |path|
|
43
|
+
::File.directory?(path)
|
44
|
+
end
|
45
|
+
|
46
|
+
full_path = ::File.expand_path(path)
|
47
|
+
extract_to = full_path.split(::File::SEPARATOR).last
|
48
|
+
extraction_mode = ::File.stat(full_path).mode
|
49
|
+
|
50
|
+
Archive.new(extract_to, extraction_mode).tap do |archive|
|
51
|
+
directories.map do |directory|
|
52
|
+
[
|
53
|
+
directory.sub(/^#{Regexp.escape(path)}\/?/, ''),
|
54
|
+
::File.stat(directory).mode
|
55
|
+
]
|
56
|
+
end.each do |directory, mode|
|
57
|
+
archive.dir(directory, mode)
|
29
58
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
59
|
+
|
60
|
+
files.each do |file|
|
61
|
+
mode = ::File.stat(file).mode
|
62
|
+
|
63
|
+
::File.open(file, 'rb') do |io|
|
64
|
+
content = io.read
|
65
|
+
|
66
|
+
archive.file(file.sub(/^#{Regexp.escape(path)}\/?/, ''), mode) do
|
67
|
+
content
|
68
|
+
end
|
69
|
+
end
|
33
70
|
end
|
71
|
+
|
72
|
+
block.call(archive) if block
|
34
73
|
end
|
35
74
|
end
|
36
75
|
end
|