file_structure 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0896ec610bd688790fad3b0f995766076ffee13148ff16ee3f9e9ae7a646b34
4
- data.tar.gz: e3a445ce085c12d7a7971dbed9469b48247d0a130fdb91acb2b28696f84d9a62
3
+ metadata.gz: dae1876a15464dfd56776c428808812ccf15c3b8c8a3b95e75c2fe5da9d6421e
4
+ data.tar.gz: 4e2d794364f2c8c9ea6179f7b8f4807394e9c0757c1ea8d4c443dc9b508c8730
5
5
  SHA512:
6
- metadata.gz: 48cd8e05e96096a21265e5c3e69f7bc5bb2bf11e5094984006f938dc79fd186fcbe8ac9f97d31d11a431db756f940780741924abdac37fccdc555e51bba75f6a
7
- data.tar.gz: c12c1441fd3dc451fdbe179bec7c4b0a90f8f473990de86273b9200db757aa75f173a12b35b7c071e5ccb1642b55c58aff5554666549cdafc95ba147dde4e2ee
6
+ metadata.gz: bd568fb6108962e88a0fa6f2300ed642e76f837d896f4d099f2250235e8f435f43168632d8b934be497090ef9c1ee2d2b3d9478b6fa49744c256aa6fefef7dfb
7
+ data.tar.gz: 78822356b462ca915d8f57b2e080781d8a0b6ade7811b2701857a35a6628076acf08e36a21754e525e9c6e70a96363ff895d428dc3c99ce79df836e2ea46a6a7
data/.rubocop.yml CHANGED
@@ -34,6 +34,10 @@ Metrics/BlockLength:
34
34
  - file_structure.gemspec
35
35
  - test/**/test_*.rb
36
36
 
37
+ Metrics/ClassLength:
38
+ Exclude:
39
+ - test/**/test_*.rb
40
+
37
41
  Metrics/MethodLength:
38
42
  Max: 15 # default: 10
39
43
 
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.1.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
- Useful for creating test environment for programs that manipulate
11
- files but can be used as is for something else entirely.
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
- # /tmp/mydir
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
- fs = FileStructure.new([
26
- { type: :file, name: 'file1' },
27
- {
28
- type: :directory,
29
- name: 'dir1',
30
- children: [
31
- {
32
- type: :directory,
33
- name: 'dir2',
34
- children: [
35
- { type: :file, name: 'file2', ref: 'ref_file2', content: 'abc' }
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 # => "/tmp/mydir"
47
- fs.unmount # deletes all files in /tmp/mydir
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
- JSON.dump(fs.structure) # (bonus) easily serializable :D
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
@@ -14,7 +14,7 @@ RuboCop::RakeTask.new
14
14
 
15
15
  desc 'Generate documentation with Yard'
16
16
  task :doc do
17
- sh 'yard doc --no-private'
17
+ sh 'yard doc'
18
18
  end
19
19
 
20
20
  task default: %i[test rubocop doc]
@@ -9,23 +9,16 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ['mail@remidurieu.dev']
10
10
 
11
11
  spec.summary = <<-TEXT
12
- Manage file structures (ie: files and directories) on the file system.
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FileStructure
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -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
- attr_reader :structure, :mountpoint
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
- Contract.assert(valid_file_structure?(structure), 'invalid file structure')
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
- Contract.assert(!mounted?, 'file structure is already mounted')
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 StandardErrror => e
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
- Contract.assert(mounted?, 'file structure is not mounted')
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
- Contract.assert(mounted?, 'file structure is not mounted')
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] current symlinks refs (recursive accumulator)
107
- def create_file_structure(dirname, structure, symlinks = {})
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.1.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-02-26 00:00:00.000000000 Z
11
+ date: 2022-03-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: |2
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: 'Manage file structures (ie: files and directories) on the file system.'
59
+ summary: Describe a file hierarchy and mount it in a directory on the file system.
64
60
  test_files: []