file_structure 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/.yardopts +1 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +9 -1
- data/README.md +58 -29
- data/Rakefile +1 -1
- data/file_structure.gemspec +3 -10
- data/lib/file_structure/dsl.rb +78 -0
- data/lib/file_structure/version.rb +1 -1
- data/lib/file_structure.rb +53 -20
- metadata +7 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dae1876a15464dfd56776c428808812ccf15c3b8c8a3b95e75c2fe5da9d6421e
|
4
|
+
data.tar.gz: 4e2d794364f2c8c9ea6179f7b8f4807394e9c0757c1ea8d4c443dc9b508c8730
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd568fb6108962e88a0fa6f2300ed642e76f837d896f4d099f2250235e8f435f43168632d8b934be497090ef9c1ee2d2b3d9478b6fa49744c256aa6fefef7dfb
|
7
|
+
data.tar.gz: 78822356b462ca915d8f57b2e080781d8a0b6ade7811b2701857a35a6628076acf08e36a21754e525e9c6e70a96363ff895d428dc3c99ce79df836e2ea46a6a7
|
data/.rubocop.yml
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private
|
data/Gemfile
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
source 'https://rubygems.org'
|
4
4
|
|
5
|
-
# Specify your gem's dependencies in file_structure.gemspec
|
6
5
|
gemspec
|
7
6
|
|
8
7
|
group :development do
|
@@ -17,6 +16,7 @@ end
|
|
17
16
|
group :test do
|
18
17
|
gem 'minitest', '~> 5.0'
|
19
18
|
gem 'minitest-reporters', '~> 1.5'
|
19
|
+
gem 'simplecov', '~> 0.21.2', require: false
|
20
20
|
end
|
21
21
|
|
22
22
|
group :development, :test do
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
file_structure (0.
|
4
|
+
file_structure (0.2.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -12,6 +12,7 @@ GEM
|
|
12
12
|
debug (1.4.0)
|
13
13
|
irb (>= 1.3.6)
|
14
14
|
reline (>= 0.2.7)
|
15
|
+
docile (1.4.0)
|
15
16
|
io-console (0.5.11)
|
16
17
|
irb (1.4.1)
|
17
18
|
reline (>= 0.3.0)
|
@@ -49,6 +50,12 @@ GEM
|
|
49
50
|
rubocop-rake (0.6.0)
|
50
51
|
rubocop (~> 1.0)
|
51
52
|
ruby-progressbar (1.11.0)
|
53
|
+
simplecov (0.21.2)
|
54
|
+
docile (~> 1.1)
|
55
|
+
simplecov-html (~> 0.11)
|
56
|
+
simplecov_json_formatter (~> 0.1)
|
57
|
+
simplecov-html (0.12.3)
|
58
|
+
simplecov_json_formatter (0.1.4)
|
52
59
|
unicode-display_width (2.1.0)
|
53
60
|
webrick (1.7.0)
|
54
61
|
yard (0.9.27)
|
@@ -67,6 +74,7 @@ DEPENDENCIES
|
|
67
74
|
rubocop-minitest (~> 0.17.2)
|
68
75
|
rubocop-performance (~> 1.13)
|
69
76
|
rubocop-rake (~> 0.6.0)
|
77
|
+
simplecov (~> 0.21.2)
|
70
78
|
yard (~> 0.9.27)
|
71
79
|
|
72
80
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -1,20 +1,47 @@
|
|
1
1
|
# file_structure
|
2
2
|
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/file_structure.svg)](https://badge.fury.io/rb/file_structure)
|
3
4
|
[![Test](https://github.com/durierem/file_structure/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/durierem/file_structure/actions/workflows/test.yml)
|
4
5
|
[![Lint](https://github.com/durierem/file_structure/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/durierem/file_structure/actions/workflows/lint.yml)
|
5
6
|
|
6
|
-
Define a file structure with a `Hash` with support for files, file content,
|
7
|
-
directories and symlinks across the structure. Mount and unmount the structure
|
8
|
-
in a directory on the file system.
|
9
7
|
|
10
|
-
|
11
|
-
|
8
|
+
Describe a file hierarchy and mount it in a directory on the file system.
|
9
|
+
|
10
|
+
## About
|
11
|
+
|
12
|
+
This gem was extracted from another project tests for which structures
|
13
|
+
containing files, directories and symlinks had to be easily reacreated on the
|
14
|
+
fly.
|
15
|
+
|
16
|
+
Though it *is* useful in the context of testing, `file_structure` does not make
|
17
|
+
assumptions about what it is being used for and deliberately does not handle
|
18
|
+
things such as temporary file structures or mock file structures.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'file_structure'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle install
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install file_structure
|
35
|
+
|
12
36
|
|
13
37
|
## Usage
|
14
38
|
|
39
|
+
Visit the [API documentation](https://www.rubydoc.info/github/durierem/file_structure/)
|
40
|
+
for more details.
|
41
|
+
|
15
42
|
```ruby
|
16
43
|
# Example creating the following file hierarchy:
|
17
|
-
# /
|
44
|
+
# /home/john/mydir
|
18
45
|
# ├── dir1
|
19
46
|
# │ ├── dir2
|
20
47
|
# │ │ └── file2
|
@@ -22,31 +49,33 @@ files but can be used as is for something else entirely.
|
|
22
49
|
# │ └── link_to_file2 -> /tmp/mydir/dir1/dir2/file2
|
23
50
|
# └── file1
|
24
51
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
{ type: :file, name: 'file3' },
|
39
|
-
{ type: :symlink, name: 'link_to_file2', to: 'ref_file2' }
|
40
|
-
]
|
41
|
-
}
|
42
|
-
])
|
43
|
-
|
44
|
-
fs.mount('/tmp/mydir') # also creates the /tmp/mydir directory if it doesn't exist
|
52
|
+
# Use the DSL to easily describe the structure
|
53
|
+
fs = FileStructure.build do
|
54
|
+
file 'file1'
|
55
|
+
directory 'dir1' do
|
56
|
+
directory 'dir2' do
|
57
|
+
file 'file2'
|
58
|
+
end
|
59
|
+
file 'file3'
|
60
|
+
symlink 'link_to_file2', to: 'file2'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
fs.mount('/home/john/mydir') # also creates the directory if it doesn't exist
|
45
65
|
fs.mounted? # => true
|
46
|
-
fs.mountpoint # => "/
|
47
|
-
fs.
|
66
|
+
fs.mountpoint # => "/home/john/mydir"
|
67
|
+
fs.path_for(:dir1, :file3) # => /home/john/mydir/dir1/file3
|
68
|
+
fs.unmount # deletes all files in /home/john/mydir
|
69
|
+
|
70
|
+
# Bonus tip 1: can be mounted in a temporary directory
|
71
|
+
Dir.mktmpdir do |dirname|
|
72
|
+
fs.mount(dir)
|
73
|
+
# do stuff
|
74
|
+
fs.unmount
|
75
|
+
end
|
48
76
|
|
49
|
-
|
77
|
+
# Bonus tip 2: easily serializable structure (who knows what could be done with this :O)
|
78
|
+
JSON.dump(fs.structure)
|
50
79
|
```
|
51
80
|
|
52
81
|
## License
|
data/Rakefile
CHANGED
data/file_structure.gemspec
CHANGED
@@ -9,23 +9,16 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.email = ['mail@remidurieu.dev']
|
10
10
|
|
11
11
|
spec.summary = <<-TEXT
|
12
|
-
|
13
|
-
TEXT
|
14
|
-
spec.description = <<-TEXT
|
15
|
-
Describe a file structure with a Ruby Hash. Supports files, file content,
|
16
|
-
directories and symlinks across the structure.
|
17
|
-
|
18
|
-
Mount and unmount the desired structured in a directory on the file system.
|
19
|
-
|
20
|
-
Useful for creating test environment for programs that manipulate
|
21
|
-
files but can be used as is for something else entirely.
|
12
|
+
Describe a file hierarchy and mount it in a directory on the file system.
|
22
13
|
TEXT
|
14
|
+
spec.description = spec.summary
|
23
15
|
spec.homepage = 'https://github.com/durierem/file_structure'
|
24
16
|
spec.license = 'MIT'
|
25
17
|
spec.required_ruby_version = '>= 2.6.0'
|
26
18
|
|
27
19
|
spec.metadata['homepage_uri'] = spec.homepage
|
28
20
|
spec.metadata['source_code_uri'] = spec.homepage
|
21
|
+
spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/github/durierem/file_structure/'
|
29
22
|
|
30
23
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
31
24
|
`git ls-files -z`.split("\x0").reject do |f|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class FileStructure
|
4
|
+
# Provide a DSL to easily create file structure definitions.
|
5
|
+
#
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# structure = FileStructure::DSL.eval do
|
10
|
+
# directory 'dir_a' do
|
11
|
+
# file 'file_a'
|
12
|
+
# symlink 'point_to_file_c', to: 'file_c_ref'
|
13
|
+
# directory 'dir_b' do
|
14
|
+
# file 'file_b'
|
15
|
+
# file 'file_c', ref: 'file_c_ref'
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
# # => [{ type: :directory, name: 'dir_a', children: [ ... ] }]
|
20
|
+
class DSL
|
21
|
+
# @return [Hash] the resulting file structure definition
|
22
|
+
attr_reader :structure
|
23
|
+
|
24
|
+
# Return the file structure definition builded with the given block.
|
25
|
+
#
|
26
|
+
# @see structure
|
27
|
+
def self.eval(&block)
|
28
|
+
dsl = new
|
29
|
+
dsl.instance_eval(&block)
|
30
|
+
dsl.structure
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@structure = []
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a file definition to the parent structure.
|
38
|
+
#
|
39
|
+
# @param name [String] the name of the file
|
40
|
+
# @param content [String] the content of the file
|
41
|
+
# @param ref [String] the reference to use for symlinks
|
42
|
+
# @return [Hash] the created file definition
|
43
|
+
def file(name, content: nil, ref: name)
|
44
|
+
file = { type: :file, name: name }
|
45
|
+
file[:content] = content if content
|
46
|
+
file[:ref] = ref
|
47
|
+
@structure << file and return file
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a symlink to the parent structure.
|
51
|
+
#
|
52
|
+
# @param name [String] the name of the symlink
|
53
|
+
# @param to [String] the reference of the file to point to
|
54
|
+
# @return [Hash] the created symlink definition
|
55
|
+
def symlink(name, to:)
|
56
|
+
symlink = { type: :symlink, name: name, to: to }
|
57
|
+
@structure << symlink and return symlink
|
58
|
+
end
|
59
|
+
|
60
|
+
# Add a directory to the parent structure.
|
61
|
+
#
|
62
|
+
# @param name [String] the name of the directory
|
63
|
+
# @param ref [String] the reference to use for symlinks
|
64
|
+
# @return [Hash] the created directory definition
|
65
|
+
def directory(name, ref: name, &block)
|
66
|
+
dsl = self.class.new
|
67
|
+
dsl.instance_eval(&block)
|
68
|
+
children = dsl.structure
|
69
|
+
directory = {
|
70
|
+
type: :directory,
|
71
|
+
name: name,
|
72
|
+
children: children,
|
73
|
+
ref: ref
|
74
|
+
}
|
75
|
+
@structure << directory and return directory
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/file_structure.rb
CHANGED
@@ -2,26 +2,38 @@
|
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
4
|
require_relative 'file_structure/contract'
|
5
|
+
require_relative 'file_structure/dsl'
|
5
6
|
require_relative 'file_structure/validator'
|
6
7
|
require_relative 'file_structure/version'
|
7
8
|
|
8
|
-
# Manage files, directories and links described with a file structure
|
9
|
-
# definition as a Ruby Hash. A valid file structure definition is an Array
|
10
|
-
# of Hashes with the following possible file definitions:
|
11
|
-
#
|
12
|
-
# rubocop:disable Layout/LineLength
|
13
|
-
# @example
|
14
|
-
# { name: 'myfile', type: :file, content: 'abc', ref: 'fileref'} # :content and :ref are optional
|
15
|
-
# { name: 'mydir' type: :directory, children: [<another valid file structure definition>], ref: 'dirref' } # :ref is optional
|
16
|
-
# { name: 'mylink', type: :link, to: 'fileref' } # link to 'myfile' refereced earlier by 'fileref'
|
17
|
-
# rubocop:enable Layout/LineLength
|
18
9
|
class FileStructure
|
19
|
-
|
10
|
+
include Contract
|
11
|
+
|
12
|
+
# @return [Hash] the file structure definition
|
13
|
+
attr_reader :structure
|
14
|
+
|
15
|
+
# @return [String, nil] the current mountpoint
|
16
|
+
attr_reader :mountpoint
|
17
|
+
|
18
|
+
# Build a new {FileStructure} using the {FileStructure::DSL}.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# FileStructure.build do
|
22
|
+
# dir 'foo' do
|
23
|
+
# file 'bar'
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @see FileStructure::DSL
|
28
|
+
# @return [FileStructure]
|
29
|
+
def self.build(&block)
|
30
|
+
new(FileStructure::DSL.eval(&block))
|
31
|
+
end
|
20
32
|
|
21
33
|
# @param structure [Array<Hash>] a valid file structure definition.
|
22
34
|
# @raise [AssertionError] if the file structure is invalid
|
23
35
|
def initialize(structure)
|
24
|
-
|
36
|
+
assert(valid_file_structure?(structure), 'invalid file structure')
|
25
37
|
|
26
38
|
@structure = structure
|
27
39
|
@mountpoint = nil
|
@@ -34,13 +46,13 @@ class FileStructure
|
|
34
46
|
# @return void
|
35
47
|
# @see unmount
|
36
48
|
def mount(dirname)
|
37
|
-
|
49
|
+
assert(!mounted?, 'file structure is already mounted')
|
38
50
|
|
39
51
|
mountpoint = File.absolute_path(dirname)
|
40
52
|
FileUtils.mkdir_p(mountpoint) unless Dir.exist?(mountpoint)
|
41
53
|
begin
|
42
54
|
create_file_structure(mountpoint, @structure)
|
43
|
-
rescue
|
55
|
+
rescue StandardError => e
|
44
56
|
FileUtils.rm_r(Dir.glob("#{mountpoint}/*")) # clear residuals
|
45
57
|
raise e
|
46
58
|
end
|
@@ -52,7 +64,7 @@ class FileStructure
|
|
52
64
|
# @return void
|
53
65
|
# @see mount
|
54
66
|
def unmount
|
55
|
-
|
67
|
+
assert(mounted?, 'file structure is not mounted')
|
56
68
|
|
57
69
|
FileUtils.rm_r(Dir.glob("#{@mountpoint}/*"))
|
58
70
|
@mountpoint = nil
|
@@ -70,10 +82,11 @@ class FileStructure
|
|
70
82
|
#
|
71
83
|
# @param args [Symbol, String Array<Symbol, String>] the recursive names to
|
72
84
|
# the desired file or directory
|
73
|
-
# @return [String] the full path to the specified file/directory
|
85
|
+
# @return [String] the full path to the specified file/directory if found
|
86
|
+
# @return [nil] if no file/directory has been found
|
74
87
|
# @raise [AssertionError] if the file structure is not mounted
|
75
88
|
def path_for(*args)
|
76
|
-
|
89
|
+
assert(mounted?, 'file structure is not mounted')
|
77
90
|
|
78
91
|
finder = [*args].flatten.map(&:to_sym)
|
79
92
|
build_path(finder, @structure)
|
@@ -103,14 +116,16 @@ class FileStructure
|
|
103
116
|
|
104
117
|
# @param dirname [String] root directory
|
105
118
|
# @param structure [Array] file structure definition
|
106
|
-
# @param symlinks [Hash]
|
107
|
-
|
119
|
+
# @param symlinks [Hash<ref, path>] symlinks map (don't use directly)
|
120
|
+
# @return void
|
121
|
+
def create_file_structure(dirname, structure, symlinks = nil)
|
122
|
+
symlinks = extract_symlinks_map(dirname, structure) if symlinks.nil?
|
123
|
+
|
108
124
|
structure.each do |element|
|
109
125
|
path = File.join(File.absolute_path(dirname), element[:name])
|
110
126
|
case element[:type]
|
111
127
|
when :file
|
112
128
|
File.write(path, element[:content])
|
113
|
-
symlinks.merge!(element[:ref] => path) if element[:ref]
|
114
129
|
when :symlink
|
115
130
|
FileUtils.symlink(symlinks[element[:to]], path)
|
116
131
|
when :directory
|
@@ -119,4 +134,22 @@ class FileStructure
|
|
119
134
|
end
|
120
135
|
end
|
121
136
|
end
|
137
|
+
|
138
|
+
# @param dirname [String] root directory
|
139
|
+
# @param structure [Array] file structure definition
|
140
|
+
# @param result [Hash] recursive accumulator (don't use directly)
|
141
|
+
# @return [Hash<ref, path>] the resulting symlinks map
|
142
|
+
def extract_symlinks_map(dirname, structure, result = {})
|
143
|
+
structure.each do |element|
|
144
|
+
next unless element[:ref]
|
145
|
+
|
146
|
+
path = File.join(File.absolute_path(dirname), element[:name])
|
147
|
+
result[element[:ref]] = path
|
148
|
+
|
149
|
+
next unless element[:type] == :directory
|
150
|
+
|
151
|
+
extract_symlinks_map(dirname, element[:children], result)
|
152
|
+
end
|
153
|
+
result
|
154
|
+
end
|
122
155
|
end
|
metadata
CHANGED
@@ -1,23 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: file_structure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rémi Durieu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
Describe a file structure with a Ruby Hash. Supports files, file content,
|
15
|
-
directories and symlinks across the structure.
|
16
|
-
|
17
|
-
Mount and unmount the desired structured in a directory on the file system.
|
18
|
-
|
19
|
-
Useful for creating test environment for programs that manipulate
|
20
|
-
files but can be used as is for something else entirely.
|
13
|
+
description: Describe a file hierarchy and mount it in a directory on the file system.
|
21
14
|
email:
|
22
15
|
- mail@remidurieu.dev
|
23
16
|
executables: []
|
@@ -25,6 +18,7 @@ extensions: []
|
|
25
18
|
extra_rdoc_files: []
|
26
19
|
files:
|
27
20
|
- ".rubocop.yml"
|
21
|
+
- ".yardopts"
|
28
22
|
- Gemfile
|
29
23
|
- Gemfile.lock
|
30
24
|
- LICENSE.txt
|
@@ -33,6 +27,7 @@ files:
|
|
33
27
|
- file_structure.gemspec
|
34
28
|
- lib/file_structure.rb
|
35
29
|
- lib/file_structure/contract.rb
|
30
|
+
- lib/file_structure/dsl.rb
|
36
31
|
- lib/file_structure/validator.rb
|
37
32
|
- lib/file_structure/version.rb
|
38
33
|
homepage: https://github.com/durierem/file_structure
|
@@ -41,6 +36,7 @@ licenses:
|
|
41
36
|
metadata:
|
42
37
|
homepage_uri: https://github.com/durierem/file_structure
|
43
38
|
source_code_uri: https://github.com/durierem/file_structure
|
39
|
+
documentation_uri: https://www.rubydoc.info/github/durierem/file_structure/
|
44
40
|
rubygems_mfa_required: 'true'
|
45
41
|
post_install_message:
|
46
42
|
rdoc_options: []
|
@@ -60,5 +56,5 @@ requirements: []
|
|
60
56
|
rubygems_version: 3.3.7
|
61
57
|
signing_key:
|
62
58
|
specification_version: 4
|
63
|
-
summary:
|
59
|
+
summary: Describe a file hierarchy and mount it in a directory on the file system.
|
64
60
|
test_files: []
|