and_feathers 0.0.1 → 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|