mustermann-fileutils 0.4.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f3a4166e01feb70f7f2ff3638a75485a5b7dac48
4
+ data.tar.gz: 3e30d5bce9ecaa69a9c06710553ac9083a1600b9
5
+ SHA512:
6
+ metadata.gz: 2dce4ceac77fff3bb5d4f6432c13051d7e921535dee68fcb927b9b547b782e8e249b9cc2478a90fbf3bfd5aa5ce12cd4f9113ee3f0c4169889ec590f5ff7d4cc
7
+ data.tar.gz: fffe449bde8a545785cd3102520fb892241e5e130728e322107d9482e504c7cd94e8dda958cd200307366a26678c4aa44d24643d52cfc103c5c4318e9af28989
@@ -0,0 +1,57 @@
1
+ # FileUtils for Mustermann
2
+
3
+ This gem implements efficient file system operations for Mustermann patterns.
4
+
5
+ ## Globbing
6
+
7
+ All operations work on a list of files described by one or more pattern.
8
+
9
+ ``` ruby
10
+ require 'mustermann/file_utils'
11
+
12
+ Mustermann::FileUtils[':base.:ext'] # => ['example.txt']
13
+
14
+ Mustermann::FileUtils.glob(':base.:ext') do |file, params|
15
+ file # => "example.txt"
16
+ params # => {"base"=>"example", "ext"=>"txt"}
17
+ end
18
+ ```
19
+
20
+ To avoid having to loop over all files and see if they match, it will generate a glob pattern resembling the Mustermann pattern as closely as possible.
21
+
22
+ ``` ruby
23
+ require 'mustermann/file_utils'
24
+
25
+ Mustermann::FileUtils.glob_pattern('/:name') # => '/*'
26
+ Mustermann::FileUtils.glob_pattern('src/:path/:file.(js|rb)') # => 'src/**/*/*.{js,rb}'
27
+ Mustermann::FileUtils.glob_pattern('{a,b}/*', type: :shell) # => '{a,b}/*'
28
+
29
+ pattern = Mustermann.new('/foo/:page', '/bar/:page') # => #<Mustermann::Composite:...>
30
+ Mustermann::FileUtils.glob_pattern(pattern) # => "{/foo/*,/bar/*}"
31
+ ```
32
+
33
+ ## Mapping
34
+
35
+ It is also possible to search for files and have their paths mapped onto another path in one method call:
36
+
37
+ ``` ruby
38
+ require 'mustermann/file_utils'
39
+
40
+ Mustermann::FileUtils.glob_map(':base.:ext' => ':base.bak.:ext') # => {'example.txt' => 'example.bak.txt'}
41
+ Mustermann::FileUtils.glob_map(':base.:ext' => :base) { |file, mapped| mapped } # => ['example']
42
+ ```
43
+
44
+ This mechanism allows things like copying, renaming and linking files:
45
+
46
+ ``` ruby
47
+ require 'mustermann/file_utils'
48
+
49
+ # copies example.txt to example.bak.txt
50
+ Mustermann::FileUtils.cp(':base.:ext' => ':base.bak.:ext')
51
+
52
+ # copies Foo.app/example.txt to Foo.back.app/example.txt
53
+ Mustermann::FileUtils.cp_r(':base.:ext' => ':base.bak.:ext')
54
+
55
+ # creates a symbolic link from bin/example to lib/example.rb
56
+ Mustermann::FileUtils.ln_s('lib/:name.rb' => 'bin/:name')
57
+ ```
@@ -0,0 +1,217 @@
1
+ require 'mustermann'
2
+ require 'mustermann/file_utils/glob_pattern'
3
+ require 'mustermann/mapper'
4
+ require 'fileutils'
5
+
6
+ module Mustermann
7
+ # Implements handy file operations using patterns.
8
+ module FileUtils
9
+ extend self
10
+
11
+ # Turn a Mustermann pattern into glob pattern.
12
+ #
13
+ # @example
14
+ # require 'mustermann/file_utils'
15
+ #
16
+ # Mustermann::FileUtils.glob_pattern('/:name') # => '/*'
17
+ # Mustermann::FileUtils.glob_pattern('src/:path/:file.(js|rb)') # => 'src/**/*/*.{js,rb}'
18
+ # Mustermann::FileUtils.glob_pattern('{a,b}/*', type: :shell) # => '{a,b}/*'
19
+ #
20
+ # pattern = Mustermann.new('/foo/:page', '/bar/:page') # => #<Mustermann::Composite:...>
21
+ # Mustermann::FileUtils.glob_pattern(pattern) # => "{/foo/*,/bar/*}"
22
+ #
23
+ # @param [Object] pattern the object to turn into a glob pattern.
24
+ # @return [String] the glob pattern
25
+ def glob_pattern(*pattern, **options)
26
+ pattern_with_glob_pattern(*pattern, **options).last
27
+ end
28
+
29
+ # Uses the given pattern(s) to search for files and directories.
30
+ #
31
+ # @example
32
+ # require 'mustermann/file_utils'
33
+ # Mustermann::FileUtils.glob(':base.:ext') # => ['example.txt']
34
+ #
35
+ # Mustermann::FileUtils.glob(':base.:ext') do |file, params|
36
+ # file # => "example.txt"
37
+ # params # => {"base"=>"example", "ext"=>"txt"}
38
+ # end
39
+ def glob(*pattern, **options, &block)
40
+ raise ArgumentError, "no pattern given" if pattern.empty?
41
+ pattern, glob_pattern = pattern_with_glob_pattern(*pattern, **options)
42
+ results = [] unless block
43
+ Dir.glob(glob_pattern) do |result|
44
+ next unless params = pattern.params(result)
45
+ block ? block[result, params] : results << result
46
+ end
47
+ results
48
+ end
49
+
50
+ # Allows to search for files an map these onto other strings.
51
+ #
52
+ # @example
53
+ # require 'mustermann/file_utils'
54
+ #
55
+ # Mustermann::FileUtils.glob_map(':base.:ext' => ':base.bak.:ext') # => {'example.txt' => 'example.bak.txt'}
56
+ # Mustermann::FileUtils.glob_map(':base.:ext' => :base) { |file, mapped| mapped } # => ['example']
57
+ #
58
+ # @see Mustermann::Mapper
59
+ def glob_map(map = {}, **options, &block)
60
+ map = Mapper === map ? map : Mapper.new(map, **options)
61
+ mapped = glob(*map.to_h.keys).map { |f| [f, unescape(map[f])] }
62
+ block ? mapped.map(&block) : Hash[mapped]
63
+ end
64
+
65
+ # Copies files based on a pattern mapping.
66
+ #
67
+ # @example
68
+ # require 'mustermann/file_utils'
69
+ #
70
+ # # copies example.txt to example.bak.txt
71
+ # Mustermann::FileUtils.cp(':base.:ext' => ':base.bak.:ext')
72
+ #
73
+ # @see #glob_map
74
+ def cp(map = {}, recursive: false, **options)
75
+ utils_opts, opts = split_options(:preserve, :dereference_root, :remove_destination, **options)
76
+ cp_method = recursive ? :cp_r : :cp
77
+ glob_map(map, **opts) { |o,n| f.send(cp_method, o, n, **utils_opts) }
78
+ end
79
+
80
+
81
+ # Copies files based on a pattern mapping, recursively.
82
+ #
83
+ # @example
84
+ # require 'mustermann/file_utils'
85
+ #
86
+ # # copies Foo.app/example.txt to Foo.back.app/example.txt
87
+ # Mustermann::FileUtils.cp_r(':base.:ext' => ':base.bak.:ext')
88
+ #
89
+ # @see #glob_map
90
+ def cp_r(map = {}, **options)
91
+ cp(map, recursive: true, **options)
92
+ end
93
+
94
+ # Moves files based on a pattern mapping.
95
+ #
96
+ # @example
97
+ # require 'mustermann/file_utils'
98
+ #
99
+ # # moves example.txt to example.bak.txt
100
+ # Mustermann::FileUtils.mv(':base.:ext' => ':base.bak.:ext')
101
+ #
102
+ # @see #glob_map
103
+ def mv(map = {}, **options)
104
+ utils_opts, opts = split_options(**options)
105
+ glob_map(map, **opts) { |o,n| f.mv(o, n, **utils_opts) }
106
+ end
107
+
108
+
109
+ # Creates links based on a pattern mapping.
110
+ #
111
+ # @example
112
+ # require 'mustermann/file_utils'
113
+ #
114
+ # # creates a link from bin/example to lib/example.rb
115
+ # Mustermann::FileUtils.ln('lib/:name.rb' => 'bin/:name')
116
+ #
117
+ # @see #glob_map
118
+ def ln(map = {}, symbolic: false, **options)
119
+ utils_opts, opts = split_options(**options)
120
+ link_method = symbolic ? :ln_s : :ln
121
+ glob_map(map, **opts) { |o,n| f.send(link_method, o, n, **utils_opts) }
122
+ end
123
+
124
+ # Creates symbolic links based on a pattern mapping.
125
+ #
126
+ # @example
127
+ # require 'mustermann/file_utils'
128
+ #
129
+ # # creates a symbolic link from bin/example to lib/example.rb
130
+ # Mustermann::FileUtils.ln_s('lib/:name.rb' => 'bin/:name')
131
+ #
132
+ # @see #glob_map
133
+ def ln_s(map = {}, **options)
134
+ ln(map, symbolic: true, **options)
135
+ end
136
+
137
+ # Creates symbolic links based on a pattern mapping.
138
+ # Overrides potentailly existing files.
139
+ #
140
+ # @example
141
+ # require 'mustermann/file_utils'
142
+ #
143
+ # # creates a symbolic link from bin/example to lib/example.rb
144
+ # Mustermann::FileUtils.ln_sf('lib/:name.rb' => 'bin/:name')
145
+ #
146
+ # @see #glob_map
147
+ def ln_sf(map = {}, **options)
148
+ ln(map, symbolic: true, force: true, **options)
149
+ end
150
+
151
+
152
+ # Splits options into those meant for Mustermann and those
153
+ # meant for ::FileUtils.
154
+ #
155
+ # @!visibility private
156
+ def split_options(*utils_option_names, **options)
157
+ utils_options, pattern_options = {}, {}
158
+ utils_option_names += %i[force noop verbose]
159
+
160
+ options.each do |key, value|
161
+ list = utils_option_names.include?(key) ? utils_options : pattern_options
162
+ list[key] = value
163
+ end
164
+
165
+ [utils_options, pattern_options]
166
+ end
167
+
168
+ # Create a Mustermann pattern from whatever the input is and turn it into
169
+ # a glob pattern.
170
+ #
171
+ # @!visibility private
172
+ def pattern_with_glob_pattern(*pattern, **options)
173
+ options[:uri_decode] ||= false
174
+ pattern = Mustermann.new(*pattern.flatten, **options)
175
+ @glob_patterns ||= {}
176
+ @glob_patterns[pattern] ||= GlobPattern.generate(pattern)
177
+ [pattern, @glob_patterns[pattern]]
178
+ end
179
+
180
+ # The FileUtils method to use.
181
+ # @!visibility private
182
+ def f
183
+ ::FileUtils
184
+ end
185
+
186
+ # Unescape an URI escaped string.
187
+ # @!visibility private
188
+ def unescape(string)
189
+ @uri ||= URI::Parser.new
190
+ @uri.unescape(string)
191
+ end
192
+
193
+ # Create a new version of Mustermann::FileUtils using a different ::FileUtils module.
194
+ # @see DryRun
195
+ # @!visibility private
196
+ def with_file_utils(&block)
197
+ Module.new do
198
+ include Mustermann::FileUtils
199
+ define_method(:f, &block)
200
+ private(:f)
201
+ extend self
202
+ end
203
+ end
204
+
205
+ private :pattern_with_glob_pattern, :split_options, :f, :unescape
206
+
207
+ alias_method :copy, :cp
208
+ alias_method :move, :mv
209
+ alias_method :link, :ln
210
+ alias_method :symlink, :ln_s
211
+ alias_method :[], :glob
212
+
213
+ DryRun ||= with_file_utils { ::FileUtils::DryRun }
214
+ NoWrite ||= with_file_utils { ::FileUtils::NoWrite }
215
+ Verbose ||= with_file_utils { ::FileUtils::Verbose }
216
+ end
217
+ end
@@ -0,0 +1,39 @@
1
+ require 'mustermann/ast/translator'
2
+
3
+ module Mustermann
4
+ module FileUtils
5
+ # AST Translator to turn Mustermann patterns into glob patterns.
6
+ # @!visibility private
7
+ class GlobPattern < Mustermann::AST::Translator
8
+ # Character that need to be escaped in glob patterns.
9
+ # @!visibility private
10
+ ESCAPE = %w([ ] { } * ** \\)
11
+
12
+ # Turn a Mustermann pattern into glob pattern.
13
+ # @param [#to_glob, #to_ast, Object] pattern the object to turn into a glob pattern.
14
+ # @return [String] the glob pattern
15
+ # @!visibility private
16
+ def self.generate(pattern)
17
+ return pattern.to_glob if pattern.respond_to? :to_glob
18
+ return new.translate(pattern.to_ast) if pattern.respond_to? :to_ast
19
+ return "**/*" unless pattern.is_a? Mustermann::Composite
20
+ "{#{pattern.patterns.map { |p| generate(p) }.join(',')}}"
21
+ end
22
+
23
+ translate(:root, :group, :expression) { t(payload) || "" }
24
+ translate(:separator, :char) { t.escape(payload) }
25
+ translate(:capture) { constraint ? "**/*" : "*" }
26
+ translate(:optional) { "{#{t(payload)},}" }
27
+ translate(:named_splat, :splat) { "**/*" }
28
+ translate(:with_look_ahead) { t(head) + t(payload) }
29
+ translate(:union) { "{#{payload.map { |e| t(e) }.join(',')}}" }
30
+ translate(Array) { map { |e| t(e) }.join }
31
+
32
+ # Escape with a slash rather than URI escaping.
33
+ # @!visibility private
34
+ def escape(char)
35
+ ESCAPE.include?(char) ? "\\#{char}" : char
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1 @@
1
+ require 'mustermann/file_utils'
@@ -0,0 +1,18 @@
1
+ $:.unshift File.expand_path("../../mustermann/lib", __FILE__)
2
+ require "mustermann/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "mustermann-fileutils"
6
+ s.version = Mustermann::VERSION
7
+ s.author = "Konstantin Haase"
8
+ s.email = "konstantin.mailinglists@googlemail.com"
9
+ s.homepage = "https://github.com/rkh/mustermann"
10
+ s.summary = %q{File Utils for Mustermann}
11
+ s.description = %q{Operate efficiently on your file system using Mustermann}
12
+ s.license = 'MIT'
13
+ s.required_ruby_version = '>= 2.1.0'
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.add_dependency 'mustermann', Mustermann::VERSION
18
+ end
@@ -0,0 +1,119 @@
1
+ require 'support'
2
+ require 'mustermann/file_utils'
3
+
4
+ describe Mustermann::FileUtils do
5
+ subject(:utils) { Mustermann::FileUtils }
6
+ include FileUtils
7
+
8
+ before do
9
+ @pwd = Dir.pwd
10
+ @tmp_dir = File.join(__dir__, 'tmp')
11
+
12
+ mkdir_p(@tmp_dir)
13
+ chdir(@tmp_dir)
14
+
15
+ touch("foo.txt")
16
+ touch("foo.rb")
17
+ touch("bar.txt")
18
+ touch("bar.rb")
19
+ end
20
+
21
+ after do
22
+ chdir(@pwd) if @pwd
23
+ rm_rf(@tmp_dir) if @tmp_dir
24
+ end
25
+
26
+ describe :glob_pattern do
27
+ example { utils.glob_pattern('/:foo') .should be == '/*' }
28
+ example { utils.glob_pattern('/*foo') .should be == '/**/*' }
29
+ example { utils.glob_pattern('/(ab|c)?/:foo/d/*bar') .should be == '/{{ab,c},}/*/d/**/*' }
30
+ example { utils.glob_pattern('/a', '/b') .should be == '{/a,/b}' }
31
+ example { utils.glob_pattern('**/*', type: :shell) .should be == '**/*' }
32
+ example { utils.glob_pattern(/can't parse this/) .should be == '**/*' }
33
+ example { utils.glob_pattern('/foo', type: :identity) .should be == '/foo' }
34
+ example { utils.glob_pattern('/fo*', type: :identity) .should be == '/fo\\*' }
35
+ end
36
+
37
+ describe :glob do
38
+ example do
39
+ utils.glob(":name.txt").sort.should be == ['bar.txt', 'foo.txt']
40
+ end
41
+
42
+ example do
43
+ extensions = []
44
+ utils.glob("foo.:ext") { |file, params| extensions << params['ext'] }
45
+ extensions.sort.should be == ['rb', 'txt']
46
+ end
47
+
48
+ example do
49
+ utils.glob(":name.:ext", capture: { ext: 'rb', name: 'foo' }).should be == ['foo.rb']
50
+ end
51
+ end
52
+
53
+ describe :glob_map do
54
+ example do
55
+ utils.glob_map(':name.rb' => ':name/init.rb').should be == {
56
+ "bar.rb" => "bar/init.rb",
57
+ "foo.rb" => "foo/init.rb"
58
+ }
59
+ end
60
+
61
+ example do
62
+ result = {}
63
+ returned = utils.glob_map(':name.rb' => ':name/init.rb') { |k, v| result[v] = k.upcase }
64
+ returned.sort .should be == ["BAR.RB", "FOO.RB"]
65
+ result["bar/init.rb"] .should be == "BAR.RB"
66
+ end
67
+ end
68
+
69
+ describe :cp do
70
+ example do
71
+ utils.cp(':name.rb' => ':name.ruby', ':name.txt' => ':name.md')
72
+ File.should be_exist('foo.ruby')
73
+ File.should be_exist('bar.md')
74
+ File.should be_exist('bar.txt')
75
+ end
76
+ end
77
+
78
+ describe :cp_r do
79
+ example do
80
+ mkdir_p "foo/bar"
81
+ utils.cp_r('foo/:name' => :name)
82
+ File.should be_directory('bar')
83
+ end
84
+ end
85
+
86
+ describe :mv do
87
+ example do
88
+ utils.mv(':name.rb' => ':name.ruby', ':name.txt' => ':name.md')
89
+ File.should be_exist('foo.ruby')
90
+ File.should be_exist('bar.md')
91
+ File.should_not be_exist('bar.txt')
92
+ end
93
+ end
94
+
95
+ describe :ln do
96
+ example do
97
+ utils.ln(':name.rb' => ':name.ruby', ':name.txt' => ':name.md')
98
+ File.should be_exist('foo.ruby')
99
+ File.should be_exist('bar.md')
100
+ File.should be_exist('bar.txt')
101
+ end
102
+ end
103
+
104
+ describe :ln_s do
105
+ example do
106
+ utils.ln_s(':name.rb' => ':name.ruby', ':name.txt' => ':name.md')
107
+ File.should be_symlink('foo.ruby')
108
+ File.should be_symlink('bar.md')
109
+ File.should be_exist('bar.txt')
110
+ end
111
+ end
112
+
113
+ describe :ln_sf do
114
+ example do
115
+ utils.ln_sf(':name.rb' => ':name.txt')
116
+ File.should be_symlink('foo.txt')
117
+ end
118
+ end
119
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mustermann-fileutils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Haase
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mustermann
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.4.0
27
+ description: Operate efficiently on your file system using Mustermann
28
+ email: konstantin.mailinglists@googlemail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - lib/mustermann/file_utils.rb
35
+ - lib/mustermann/file_utils/glob_pattern.rb
36
+ - lib/mustermann/fileutils.rb
37
+ - mustermann-fileutils.gemspec
38
+ - spec/file_utils_spec.rb
39
+ homepage: https://github.com/rkh/mustermann
40
+ licenses:
41
+ - MIT
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 2.1.0
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project:
59
+ rubygems_version: 2.4.3
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: File Utils for Mustermann
63
+ test_files:
64
+ - spec/file_utils_spec.rb
65
+ has_rdoc: