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 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: []