fun_with_files 0.0.13 → 0.0.14

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,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZDc0MmI0ZDg0NWRkMTFmMTBkMGM1ZTRhZjA4MTc4OWMwOTZmN2RhNA==
5
- data.tar.gz: !binary |-
6
- OTE2Y2IxNTI0Y2U0Y2VhNjFlODlmOWE0NmM2ZTAxZjNlMzlkYzZjYg==
2
+ SHA1:
3
+ metadata.gz: cf6595c5de6273e8d7fb668ddc70d161a1354c4c
4
+ data.tar.gz: 4938516e5ad413f8ffa722985d963fcb6eb1f039
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZWFjZTJjZjkyMTUwZDU1M2MxOTcyYTM5OTYwYzQzYjlhNTBiMDAxZjJmNmU1
10
- Yjk4ZTBiYThhMTc0ZDVlZTQ2MDQzZjQ1ZmQ0NmE3MDQxZmQzNDJiMzJhZjAx
11
- YjRmNGRkNGRhZTUyMWI2ZWU4NTkzMjk3ZTQzOTdlZDhlMjYxZDg=
12
- data.tar.gz: !binary |-
13
- M2Q0Zjc1NjAxZDdlZGQ1Y2YzNzc1ZTAzYjAwZjRlYTdmM2MxMDM1ZDVjMDAy
14
- MGExZDRhMTFjYTQ4NTI1NzdiNGIzNDRkZTY4ZGRhMjYyNDM2ZjQ0YmI1Zjdl
15
- NTZlNTc3MmM1NTYwZTNhNjgxODEyNTUxYzhhMDljNzUzYTUzMTY=
6
+ metadata.gz: 09db56d93b3cde0407d1f0e7d8691ab0373158301828ee5af974c1cb259369c2f8a5c43c79eb37352760bede2d5d4d4f310887b9129aba917100109643138787
7
+ data.tar.gz: 06690ccc56d90230e7d8c55a9f9372cc74dc9f6ff1104fe7f8ea26c1efdb601b5ff221a0c4250630d40ab7e2a065cc71ff124545de1ed1a66405fc591ffbc49a
@@ -0,0 +1,50 @@
1
+ CHANGELOG
2
+ =========
3
+
4
+ v0.0.14
5
+ =======
6
+
7
+ Make symlinking actually work in a sensible way.
8
+
9
+ v0.0.12
10
+ =======
11
+
12
+ FilePath.touch() takes same options as FileUtils.touch()
13
+ FilePath.glob() now takes a block (yields files one at a time)
14
+
15
+
16
+
17
+ v0.0.9
18
+ ------
19
+
20
+ Changed the initialization procedure, which wasn't working on some systems.
21
+
22
+ v0.0.8
23
+ ------
24
+
25
+ A few new, useful functions. .ext() overloaded to take an argument, spit out the path with .arg added to the end. timestamp()
26
+
27
+ v0.0.7
28
+ ------
29
+
30
+ Added dependency on `fun_with_version_strings`. Then removed it, cuz fwvs needs fwf more. Bleh.
31
+ Added filepath.md5() => hexdigest of file contents.
32
+ Added shaXXXX functions, so the poor li'l crypto won't feel left out.
33
+
34
+
35
+ 0.0.4 - 0.0.6
36
+ -------------
37
+
38
+ I've not been so good about updating this doc.
39
+
40
+
41
+ 0.0.3
42
+ -----
43
+
44
+ Added .succession() to get all existing files within a succession group. The differentiating piece of the filename can be either a timestamp or a numeric ID.
45
+
46
+
47
+ TODO
48
+ ----
49
+
50
+ * It really makes more sense for path.basename to return a string instead of a filepath. Or is it? Why does Pathname.basename return a path? Imma cargo cult this and leave it as-is.
@@ -1,4 +1,4 @@
1
- = fun_with_files
1
+ = fun_with_files =
2
2
 
3
3
  FunWith::Files adds a bit of whimsy to your file manipulations, if that's what you're looking for.
4
4
 
@@ -34,7 +34,15 @@ To the code!
34
34
 
35
35
  # whole buchcha other goodies, yet to be documented.
36
36
 
37
- == DirectoryBuilder
37
+
38
+
39
+ === Linking files ===
40
+
41
+ While fwf.symlink and fwf.link are both backed by FileUtils.ln / FileUtils.ln_s, the defaults are somewhat different
42
+
43
+
44
+
45
+ == DirectoryBuilder ==
38
46
 
39
47
  DirectoryBuilder is a class for defining and populating a file hierarchy with relative ease. DirectoryBuilder is probably most easily demonstrated by example. Sample code:
40
48
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.13
1
+ 0.0.14
@@ -0,0 +1,5 @@
1
+ class File < IO
2
+ def fwf_filepath( *args )
3
+ FunWith::Files::FilePath.new( self.path, *args )
4
+ end
5
+ end
@@ -4,7 +4,7 @@ module FunWith
4
4
  # Describes a domain-specific language for creating and populating a
5
5
  # directory of files.
6
6
  class DirectoryBuilder
7
- attr_accessor :current_path, :current_file
7
+ attr_accessor :current_path
8
8
 
9
9
  def initialize( path )
10
10
  @paths = []
@@ -68,10 +68,16 @@ module FunWith
68
68
  end
69
69
  end
70
70
 
71
- def current_file
72
- @current_file ? FunWith::Files::FilePath.new( @current_file.path ) : nil
71
+ attr_reader :current_file
72
+
73
+ def current_file=( file )
74
+ @current_file = file.fwf_filepath
73
75
  end
74
76
 
77
+ # def current_file
78
+ # @current_file ? FunWith::Files::FilePath.new( @current_file.path ) : nil
79
+ # end
80
+
75
81
  # if file not given, the result is appended to the current file.
76
82
  def download( url, file = nil )
77
83
  if file
@@ -21,45 +21,68 @@ module FunWith
21
21
  #
22
22
  #
23
23
  def cp( *args )
24
- dest, opts = self.destination_and_options( args )
25
- FileUtils.cp_r( self, dest, narrow_options( opts, FileUtils::OPT_TABLE["cp_r"] ) )
26
- dest.fwf_filepath
24
+ destination_and_options( args ) do |dest, opts|
25
+ FileUtils.cp_r( self, dest, narrow_options( opts, FileUtils::OPT_TABLE["cp_r"] ) )
26
+ dest.fwf_filepath
27
+ end
27
28
  end
28
29
 
29
30
  alias :copy :cp
30
31
 
32
+ # Treat as a copy then a delete? Nah, that's a lot slower in some cases. Should be much more in tune with what the command line program does
31
33
  def mv( *args )
32
34
 
33
35
  end
34
36
 
37
+ alias :move :mv
38
+
35
39
 
36
- # self is the target, link is the thing linking to self
40
+ # Logic of link()
41
+ #
42
+ # self is the target, link is the filepath entry linking to the file represented by self
37
43
  # returns filepath of the new link. Will fall back to symbolic
38
- # link if self is a directory
39
- def ln( *args )
40
- self.destination_and_options( args ) do |link, opts|
41
- symlink = self.directory? || opts[:symbolic] || opts[:sym] || opts[:soft]
44
+ # link if self is a directory. Necessary directories will be created.
45
+ def link *args
46
+ self.destination_and_options( args ) do |lnk, opts|
47
+ symlink_requested = self.directory? || opts[:symbolic] || opts[:sym] || opts[:soft]
42
48
 
43
- if symlink
44
- FileUtils.ln_s( self, link, narrow_options( opts, FileUtils::OPT_TABLE["ln_s"] ) )
49
+ if symlink_requested
50
+ self.symlink lnk, opts
45
51
  else
46
- FileUtils.ln( self, link, narrow_options( opts, FileUtils::OPT_TABLE["ln"] ) )
52
+ opts = narrow_options opts, FileUtils::OPT_TABLE["ln"]
53
+
54
+ FileUtils.ln self, lnk, opts
47
55
  end
48
56
 
49
- link.fwf_filepath
57
+ lnk.fwf_filepath
50
58
  end
51
59
  end
52
60
 
53
- alias :link :ln
61
+ alias :ln :link
54
62
 
55
-
56
- def ln_s( *args )
57
- link, opts = self.destination_and_options( args )
58
- FileUtils.ln_s( self, link, narrow_options( opts, FileUtils::OPT_TABLE["ln_s"] ) )
59
- link.fwf_filepath
63
+ # * Where does the symlink live in the filesys.
64
+ # * What does it point to?
65
+ # * How does it point to the thing?
66
+ # * absolutely
67
+ # * relatively
68
+ # * custom string (programmer error hilarity ensues?)
69
+ # It can't
70
+ # What to return? The path of the symlink, or the path of the target?
71
+ #
72
+ def symlink( *args )
73
+ lnk, opts = self.destination_and_options( args )
74
+
75
+ if opts[:absolute]
76
+ lnk = lnk.fwf_filepath.expand
77
+ else
78
+ lnk = lnk.fwf_filepath
79
+ end
80
+
81
+ FileUtils.ln_s( self, lnk, narrow_options( opts, FileUtils::OPT_TABLE["ln_s"] ) )
82
+ lnk.fwf_filepath
60
83
  end
61
84
 
62
- alias :symlink :ln_s
85
+ alias :ln_s :symlink
63
86
 
64
87
 
65
88
  def file_gsub( *args, &block )
@@ -118,10 +141,11 @@ module FunWith
118
141
  end
119
142
  end
120
143
 
144
+
121
145
  protected
122
146
  def destination_and_options( args, &block )
123
147
  options = args.last.is_a?(Hash) ? args.pop : {}
124
- destination = self.find_destination_from_args( args )
148
+ destination = self._find_destination_from_args( args )
125
149
 
126
150
  if block_given?
127
151
  yield [destination, options]
@@ -140,7 +164,7 @@ module FunWith
140
164
  # If dest doesn't exist, and src (self) is a file, dest is taken to be the complete path.
141
165
  # If dest doesn't exist, and src (self) is a directory, then dest is taken to be
142
166
  # If dest is a directory and the source is a file, then the file will be copied into dest with the src's basename
143
- def find_destination_from_args( args )
167
+ def _find_destination_from_args( args )
144
168
  raise ArgumentError.new("File #{self} must exist.") unless self.exist?
145
169
 
146
170
  if args.first.is_a?(Pathname)
@@ -1,6 +1,7 @@
1
1
  module FunWith
2
2
  module Files
3
3
  class FilePath < Pathname
4
+
4
5
  SUCC_DIGIT_COUNT = 6
5
6
  DEFAULT_TIMESTAMP_FORMAT = "%Y%m%d%H%M%S%L"
6
7
 
@@ -44,50 +45,57 @@ module FunWith
44
45
  self.join(*args)
45
46
  end
46
47
 
47
- alias :exists? :exist?
48
48
 
49
49
  def doesnt_exist?
50
50
  self.exist? == false
51
51
  end
52
52
 
53
+ def not_a_file?
54
+ ! self.file?
55
+ end
56
+
57
+ alias :absent? :doesnt_exist?
58
+ alias :exists? :exist?
59
+ alias :folder? :directory?
60
+
53
61
  # If called on a file instead of a directory,
54
62
  # has the same effect as path.dirname
55
63
  def up
56
64
  self.class.new( self.join("..") ).expand
57
65
  end
58
-
66
+
59
67
  alias :down :join
60
-
68
+
61
69
  # opts:
62
- # :flags => File::FNM_CASEFOLD
63
- # File::FNM_DOTMATCH
64
- # File::FNM_NOESCAPE
65
- # File::FNM_PATHNAME
66
- # File::FNM_SYSCASE
67
- # See Dir documentation for details.
68
- # Can be given as an integer: (File::FNM_DOTMATCH | File::FNM_NOESCAPE)
70
+ # :flags => File::FNM_CASEFOLD
71
+ # File::FNM_DOTMATCH
72
+ # File::FNM_NOESCAPE
73
+ # File::FNM_PATHNAME
74
+ # File::FNM_SYSCASE
75
+ # See Dir documentation for details.
76
+ # Can be given as an integer: (File::FNM_DOTMATCH | File::FNM_NOESCAPE)
69
77
  # or as an array: [File::FNM_CASEFOLD, File::FNM_DOTMATCH]
70
- #
78
+ #
71
79
  # :class => [self.class] The class of objects you want returned (String, FilePath, etc.)
72
80
  # Should probably be a subclass of FilePath or String. Class.initialize() must accept a string
73
81
  # [representing a file path] as the sole argument.
74
82
  #
75
83
  # :recurse => [defaults true]
76
84
  # :recursive (synonym for :recurse)
77
- #
85
+ #
78
86
  # :ext => [] A single symbol, or a list containing strings/symbols representing file name extensions.
79
87
  # No leading periods kthxbai.
80
88
  # :sensitive => true : do a case sensitive search. I guess the default is an insensitive search, so
81
89
  # the default behaves similarly on Windows and Unix. Not gonna fight it.
82
90
  #
83
- # :dots => true : include dotfiles. Does not include . and ..s unless you also
84
- # specify the option :parent_and_current => true.
85
- #
91
+ # :dots => true : include dotfiles. Does not include . and ..s unless you also
92
+ # specify the option :parent_and_current => true.
93
+ #
86
94
  # If opts[:recurse] / opts[:ext] not given, the user can get the same
87
95
  # results explicitly with arguments like .glob("**", "*.rb")
88
- #
96
+ #
89
97
  # :all : if :all is the only argument, this is the same as .glob("**", "*")
90
- #
98
+ #
91
99
  # Examples:
92
100
  # @path.glob( "css", "*.css" ) # Picks up all css files in the css folder
93
101
  # @path.glob( "css", :ext => :css ) # same
@@ -95,20 +103,20 @@ module FunWith
95
103
  # @path.glob(:all) # same. Note: :all cannot be used in conjunction with :ext or any other arguments. Which may be a mistake on my part.
96
104
  # @path.glob("**", "*") # same
97
105
  # @path.entries # synonym for :all, :recursive => false
98
- #
106
+ #
99
107
  # TODO: depth argument? depth should override recurse. When extention given, recursion should default to true?
100
108
  # the find -depth argument says depth(0) is the root of the searched directory, any files beneath would be depth(1)
101
109
  def glob( *args, &block )
102
110
  args.push( :all ) if args.fwf_blank?
103
111
  opts = args.last.is_a?(Hash) ? args.pop : {}
104
-
112
+
105
113
  if args.last == :all
106
114
  all_arg_given = true
107
115
  args.pop
108
116
  else
109
117
  all_arg_given = false
110
118
  end
111
-
119
+
112
120
  flags = case (flags_given = opts.delete(:flags))
113
121
  when NilClass
114
122
  0
@@ -119,10 +127,10 @@ module FunWith
119
127
  when Integer
120
128
  flags_given
121
129
  end
122
-
130
+
123
131
  flags |= File::FNM_DOTMATCH if opts[:dots]
124
132
  flags |= File::FNM_CASEFOLD if opts[:sensitive] # case sensitive. Only applies to Windows.
125
-
133
+
126
134
  recurse = if all_arg_given
127
135
  if opts[:recursive] == false || opts[:recurse] == false
128
136
  false
@@ -132,7 +140,7 @@ module FunWith
132
140
  else
133
141
  opts[:recursive] == true || opts[:recurse] == true || false
134
142
  end
135
-
143
+
136
144
  if all_arg_given
137
145
  if recurse
138
146
  args = ["**", "*"]
@@ -155,15 +163,15 @@ module FunWith
155
163
  nil
156
164
  end
157
165
  end
158
-
166
+
159
167
  args.push( extensions ) if extensions
160
168
  end
161
-
169
+
162
170
  class_to_return = opts[:class] || self.class
163
-
171
+
164
172
  files = Dir.glob( self.join(*args), flags ).map{ |f| class_to_return.new( f ) }
165
173
  files.reject!{ |f| f.basename.to_s.match( /^\.\.?$/ ) } unless opts[:parent_and_current]
166
-
174
+
167
175
  if block_given?
168
176
  for file in files
169
177
  yield file
@@ -172,11 +180,11 @@ module FunWith
172
180
  files
173
181
  end
174
182
  end
175
-
183
+
176
184
  def entries
177
185
  self.glob( :recurse => false )
178
186
  end
179
-
187
+
180
188
  def expand
181
189
  self.class.new( File.expand_path( self ) )
182
190
  end
@@ -184,14 +192,14 @@ module FunWith
184
192
  # Raises error if self is a file and args present.
185
193
  # Raises error if the file is not accessible for writing, or cannot be created.
186
194
  # attempts to create a directory
187
- #
195
+ #
188
196
  # Takes an options hash as the last argument, allowing same options as FileUtils.touch
189
197
  def touch( *args, &block )
190
198
  args, opts = extract_opts_from_args( args )
191
-
199
+
192
200
  raise "Cannot create subdirectory to a file" if self.file? && args.length > 0
193
201
  touched = self.join(*args)
194
-
202
+
195
203
  dir_for_touched_file = case args.length
196
204
  when 0
197
205
  self.up
@@ -200,39 +208,63 @@ module FunWith
200
208
  when 2..Float::INFINITY
201
209
  self.join( *(args[0..-2] ) )
202
210
  end
203
-
211
+
204
212
  self.touch_dir( dir_for_touched_file, opts ) unless dir_for_touched_file.directory?
205
213
  FileUtils.touch( touched, narrow_options( opts, FileUtils::OPT_TABLE["touch"] ) )
206
-
214
+
207
215
  yield touched if block_given?
208
216
  return touched
209
217
  end
210
-
218
+
211
219
  # Takes the options of both FileUtils.touch and FileUtils.mkdir_p
212
220
  # mkdir_p options will only matter if the directory is being created.
213
221
  def touch_dir( *args, &block )
214
222
  args, opts = extract_opts_from_args( args )
215
-
223
+
216
224
  touched = self.join(*args)
217
225
  if touched.directory?
218
226
  FileUtils.touch( touched, narrow_options( opts, FileUtils::OPT_TABLE["touch"] ) ) # update access time
219
227
  else
220
228
  FileUtils.mkdir_p( touched, narrow_options( opts, FileUtils::OPT_TABLE["mkdir_p"] ) ) # create directory (and any needed parents)
221
229
  end
222
-
230
+
223
231
  yield touched if block_given?
224
232
  return touched
225
233
  end
226
-
227
- def write( content = nil, &block )
228
- File.open( self, "w" ) do |f|
229
- f << content if content
230
- if block_given?
231
- yield f
234
+
235
+ def write( *args, &block )
236
+ args, opts = extract_opts_from_args( args )
237
+
238
+ content = args.first
239
+
240
+ if content == :random
241
+ self.write_random_data( opts )
242
+ else
243
+ File.open( self, "w" ) do |f|
244
+ f << content if content
245
+ if block_given?
246
+ yield f
247
+ end
232
248
  end
233
249
  end
234
250
  end
235
-
251
+
252
+ # sz: number of bytes to write to the file
253
+ # opts[:mode] => :overwrite or :append
254
+ # seed: What number to seed the random number generator with
255
+ #
256
+ # FUTURE: May perform slowly on large sz inputs?
257
+ def write_random_data( sz, opts = {} )
258
+ rng = Random.new( opts[:seed] || Random::new_seed )
259
+ mode = opts[:mode] || :overwrite
260
+
261
+ if mode == :overwrite
262
+ self.write( rng.bytes( sz ) )
263
+ elsif mode == :append
264
+ self.append( rng.bytes( sz ) )
265
+ end
266
+ end
267
+
236
268
  def append( content = nil, &block )
237
269
  File.open( self, "a" ) do |f|
238
270
  f << content if content
@@ -241,9 +273,9 @@ module FunWith
241
273
  end
242
274
  end
243
275
  end
244
-
276
+
245
277
  # Returns a [list] of the lines in the file matching the given file. Contrast with
246
-
278
+
247
279
  def grep( regex, &block )
248
280
  return [] unless self.file?
249
281
  matching = []
@@ -251,8 +283,8 @@ module FunWith
251
283
  matching.push( line ) if line.match( regex )
252
284
  yield line if block_given?
253
285
  end
254
-
255
-
286
+
287
+
256
288
  matching
257
289
  end
258
290
 
@@ -261,23 +293,23 @@ module FunWith
261
293
  # must not have any data in it.
262
294
  def empty?
263
295
  raise Exceptions::FileDoesNotExist unless self.exist?
264
-
296
+
265
297
  if self.file?
266
298
  File.size( self ) == 0
267
299
  elsif self.directory?
268
300
  self.glob( :all ).fwf_blank?
269
301
  end
270
302
  end
271
-
303
+
272
304
  # Does not return a filepath
273
305
  def basename_no_ext
274
306
  self.basename.to_s.split(".")[0..-2].join(".")
275
307
  end
276
-
308
+
277
309
  def without_ext
278
310
  self.gsub(/\.#{self.ext}$/, '')
279
311
  end
280
-
312
+
281
313
  # Two separate modes. With no arguments given, returns the current extension as a string (not a filepath)
282
314
  # With an argument, returns the path with a .(arg) tacked onto the end. The leading period is wholly optional.
283
315
  # Does not return a filepath.
@@ -286,52 +318,58 @@ module FunWith
286
318
  if args.length == 0
287
319
  split_basename = self.basename.to_s.split(".")
288
320
  split_basename.length > 1 ? split_basename.last : ""
289
- elsif args.length == 1
290
- ext = args.first.to_s.gsub(/^\./,'')
291
- self.class.new( @path.dup + ".#{ext}" )
321
+ else
322
+
323
+ append_to_path = args.compact.map{ |ex|
324
+ ex.to_s.gsub( /^\./, '' )
325
+ }.compact.join( "." )
326
+
327
+ appended_to_path = "." + "#{append_to_path}" unless append_to_path.fwf_blank?
328
+
329
+ self.class.new( "#{@path}#{appended_to_path}" )
292
330
  end
293
331
  end
294
-
332
+
295
333
  # base, ext = @path.basename_and_ext
296
334
  def basename_and_ext
297
335
  [self.basename_no_ext, self.ext]
298
336
  end
299
-
300
-
301
-
337
+
338
+
339
+
302
340
  def dirname_and_basename
303
341
  warn("FilePath#dirname_and_basename() is deprecated. Pathname#split() already existed, and should be used instead.")
304
342
  [self.dirname, self.basename]
305
343
  end
306
-
344
+
307
345
  def dirname_and_basename_and_ext
308
346
  [self.dirname, self.basename_no_ext, self.ext]
309
347
  end
310
-
348
+
311
349
  # if it's a file, returns the immediate parent directory.
312
350
  # if it's not a file, returns itself
313
351
  def directory
314
352
  self.directory? ? self : self.dirname
315
353
  end
316
-
354
+
317
355
  def original?
318
356
  !self.symlink?
319
357
  end
320
-
358
+
321
359
  def original
322
360
  self.symlink? ? self.readlink.original : self
323
361
  end
324
-
362
+
325
363
  # Basically Pathname.relative_path_from, but you can pass in strings
326
364
  def relative_path_from( dir )
327
365
  dir = super( Pathname.new( dir ) )
328
366
  self.class.new( dir )
329
367
  end
330
-
368
+
331
369
  def fwf_filepath
332
370
  self
333
371
  end
334
-
372
+
335
373
  # Gives a sequence of files. Examples:
336
374
  # file.dat --> file.000000.dat
337
375
  # file_without_ext --> file_without_ext.000000
@@ -341,7 +379,7 @@ module FunWith
341
379
  # You can change the length of the sequence string by passing
342
380
  # in an argument, but it should always be the same value for
343
381
  # a given set of files.
344
- #
382
+ #
345
383
  # TODO: Need to get this relying on the specifier() method.
346
384
  def succ( opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false } )
347
385
  if timestamp = opts[:timestamp]
@@ -352,7 +390,7 @@ module FunWith
352
390
  timestamp = false
353
391
  digit_count = opts[:digit_count]
354
392
  end
355
-
393
+
356
394
  chunks = self.basename.to_s.split(".")
357
395
  # not yet sequence stamped, no file extension.
358
396
  if chunks.length == 1
@@ -384,17 +422,17 @@ module FunWith
384
422
 
385
423
  self.up.join( chunks.join(".") )
386
424
  end
387
-
388
-
425
+
426
+
389
427
  def timestamp( format = true, &block )
390
428
  nxt = self.succ( :timestamp => format )
391
429
  yield nxt if block_given?
392
430
  nxt
393
431
  end
394
-
432
+
395
433
  # puts a string between the main part of the basename and the extension
396
434
  # or after the basename if there is no extension. Used to describe some
397
- # file variant.
435
+ # file variant.
398
436
  # Example "/home/docs/my_awesome_screenplay.txt".fwf_filepath.specifier("final_draft")
399
437
  # => FunWith::Files::FilePath:/home/docs/my_awesome_screenplay.final_draft.txt
400
438
  #
@@ -402,16 +440,16 @@ module FunWith
402
440
  def specifier( str )
403
441
  str = str.to_s
404
442
  chunks = self.to_s.split(".")
405
-
443
+
406
444
  if chunks.length == 1
407
445
  chunks << str
408
446
  else
409
447
  chunks = chunks[0..-2] + [str] + [chunks[-1]]
410
448
  end
411
-
449
+
412
450
  chunks.join(".").fwf_filepath
413
451
  end
414
-
452
+
415
453
  # TODO: succession : enumerates a sequence of files that get passed
416
454
  # to a block in order.
417
455
  def succession( opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false } )
@@ -423,10 +461,10 @@ module FunWith
423
461
  timestamp = false
424
462
  digit_count = opts[:digit_count]
425
463
  end
426
-
464
+
427
465
  chunks = self.basename.to_s.split(".")
428
466
  glob_stamp_matcher = '[0-9]' * digit_count
429
-
467
+
430
468
  # unstamped filename, no extension
431
469
  if chunks.length == 1
432
470
  original = chunks.first
@@ -444,15 +482,15 @@ module FunWith
444
482
  original = chunks.join(".")
445
483
  stamped = [ chunks[0..-2], glob_stamp_matcher, chunks[-1] ].flatten.join(".")
446
484
  end
447
-
485
+
448
486
  [self.dirname.join(original), self.dirname.glob(stamped)].flatten
449
487
  end
450
488
 
451
489
 
452
-
490
+
453
491
  # TODO: succ_last : find the last existing file of the given sequence.
454
492
  # TODO: succ_next : find the first free file of the given sequence
455
-
493
+
456
494
  def load
457
495
  if self.directory?
458
496
  self.glob( :recursive => true, :ext => "rb" ).map(&:load)
@@ -460,12 +498,12 @@ module FunWith
460
498
  Kernel.load( self.expand )
461
499
  end
462
500
  end
463
-
501
+
464
502
  # Require ALL THE RUBY!
465
503
  # This may be a bad idea...
466
- #
504
+ #
467
505
  # Sometimes it fails to require a file because one of the necessary prerequisites
468
- # hasn't been required yet (NameError). requir catches this failure and stores
506
+ # hasn't been required yet (NameError). requir catches this failure and stores
469
507
  # the failed requirement in order to try it later. Doesn't fail until it goes through
470
508
  # a full loop where none of the required files were successful.
471
509
  def requir
@@ -474,12 +512,12 @@ module FunWith
474
512
  successfully_required = 1337 # need to break into initial loop
475
513
  failed_requirements = []
476
514
  error_messages = []
477
-
515
+
478
516
  while requirements.length > 0 && successfully_required > 0
479
517
  successfully_required = 0
480
518
  failed_requirements = []
481
519
  error_messages = []
482
-
520
+
483
521
  for requirement in requirements
484
522
  begin
485
523
  requirement.requir
@@ -489,30 +527,30 @@ module FunWith
489
527
  error_messages << "Error while requiring #{requirement} : #{e.message} (#{e.class})"
490
528
  end
491
529
  end
492
-
530
+
493
531
  requirements = failed_requirements
494
532
  end
495
-
533
+
496
534
  if failed_requirements.length > 0
497
535
  msg = "requiring directory #{self} failed:\n"
498
536
  for message in error_messages
499
537
  msg << "\n\terror message: #{message}"
500
538
  end
501
-
539
+
502
540
  raise NameError.new(msg)
503
541
  end
504
542
  else
505
543
  require self.expand.gsub( /\.rb$/, '' )
506
544
  end
507
545
  end
508
-
546
+
509
547
  def root?
510
548
  self == self.up
511
549
  end
512
-
550
+
513
551
  def descend( &block )
514
552
  path = self.clone
515
-
553
+
516
554
  if path.root?
517
555
  yield path
518
556
  else
@@ -520,10 +558,10 @@ module FunWith
520
558
  yield self
521
559
  end
522
560
  end
523
-
561
+
524
562
  def ascend( &block )
525
563
  path = self.clone
526
-
564
+
527
565
  if path.root?
528
566
  yield path
529
567
  else
@@ -531,7 +569,7 @@ module FunWith
531
569
  self.up.ascend( &block )
532
570
  end
533
571
  end
534
-
572
+
535
573
  def to_pathname
536
574
  Pathname.new( @path )
537
575
  end
@@ -546,7 +584,7 @@ module FunWith
546
584
  # end
547
585
  # # otherwise we're installing a separator
548
586
  # end
549
-
587
+
550
588
  protected
551
589
  # TODO: Need a separate API for user to call
552
590
  def _must_be_a_file
@@ -555,25 +593,25 @@ module FunWith
555
593
  raise Errno::EACCESS.new( "Can only call FunWith::Files::FilePath##{calling_method}() on an existing file.")
556
594
  end
557
595
  end
558
-
596
+
559
597
  def _must_be_a_directory
560
598
  unless self.directory?
561
599
  calling_method = caller[0][/`.*'/][1..-2]
562
600
  raise Errno::EACCESS.new( "Can only call FunWith::Files::FilePath##{calling_method}() on an existing directory.")
563
601
  end
564
602
  end
565
-
603
+
566
604
  def _must_be_writable
567
605
  unless self.writable?
568
606
  calling_method = caller[0][/`.*'/][1..-2]
569
607
  raise Errno::EACCESS.new( "Error in FunWith::Files::FilePath##{calling_method}(): #{@path} not writable.")
570
608
  end
571
609
  end
572
-
610
+
573
611
  def narrow_options( opts, keys )
574
612
  opts.keep_if{ |k,v| keys.include?( k ) }
575
613
  end
576
-
614
+
577
615
  def extract_opts_from_args( args )
578
616
  if args.last.is_a?( Hash )
579
617
  [args[0..-2], args.last ]
@@ -581,7 +619,7 @@ module FunWith
581
619
  [args, {}]
582
620
  end
583
621
  end
584
-
622
+
585
623
  def yield_and_return( obj, &block )
586
624
  yield obj if block_given?
587
625
  obj
@@ -29,12 +29,16 @@ class TestDirectoryBuilder < FunWith::Files::TestCase
29
29
  assert_equal DirectoryBuilder, b.class
30
30
  assert b.current_path
31
31
  assert b.current_path.exist?
32
+
32
33
  b.file("widdershins.txt") do |f|
34
+ assert_empty_file( f.fwf_filepath )
35
+
33
36
  f << "Hello World"
34
37
  f.flush
35
-
36
- assert b.current_file.exist?
37
- assert_equal 11, b.current_file.size
38
+
39
+ fil = b.current_file.fwf_filepath
40
+ assert_file_not_empty fil
41
+ assert_equal 11, fil.size
38
42
  end
39
43
  end
40
44
  end
@@ -85,7 +85,7 @@ class TestFileManipulation < FunWith::Files::TestCase
85
85
 
86
86
  should "symlink masterfully" do
87
87
  file = @dir.join( "original.txt" )
88
- file.write( "This is the original file" )
88
+ file.write( "I AM SPARTACUS" )
89
89
 
90
90
  clone = file.symlink( "clone.txt" )
91
91
  clone_of_clone = clone.symlink( "clone_of_clone.txt" )
@@ -93,7 +93,14 @@ class TestFileManipulation < FunWith::Files::TestCase
93
93
  assert( clone_of_clone.symlink? )
94
94
  assert_equal( file, clone_of_clone.original )
95
95
 
96
- assert_file_contents( clone_of_clone, /This is the/ )
96
+ assert_file_contents( clone_of_clone, /I AM SPARTACUS/ )
97
+
98
+ subdir = @dir.join( "subdir" ).touch_dir
99
+
100
+ subdir_clone = clone_of_clone.symlink( subdir.join( "clone.txt" ) )
101
+ subdir_clone_of_clone = subdir_clone.symlink( "clone_of_clone.txt" )
102
+
103
+ assert_file_contents( subdir_clone_of_clone, /I AM SPARTACUS/ )
97
104
  end
98
105
 
99
106
  should "do nothing when opts[:noop]" do
@@ -101,5 +108,35 @@ class TestFileManipulation < FunWith::Files::TestCase
101
108
  file.touch( :noop => true, :this_invalid_option_is_ignored => true, :also_this_one => "also ignored" )
102
109
  assert_no_file file
103
110
  end
111
+
112
+ should "pass exhaustive copypalooza" do
113
+ dir_abc = @dir.join( "A", "B", "C" ).touch_dir
114
+ dir_def = @dir.join( "D", "E", "F" ).touch_dir
115
+
116
+ file_abc = dir_abc.join( "abc.txt" )
117
+ file_abc.write( "this space for rent" )
118
+
119
+ assert_file_contents file_abc, /rent/
120
+
121
+ file_def = file_abc.cp( dir_def )
122
+
123
+ assert_match /abc\.txt/, file_def.path
124
+ assert_file_contents file_def, /rent/
125
+
126
+
127
+ # Examples: @dir.cp( "~/tmp/dir" ) would use _find_destination_from_args() to select /Users/me/tmp/dir filepath
128
+ # If it's already a file, raise an error. If the directory exists already, hmmm.... I want it to not matter, but
129
+ # it matters. And the issues raised by each operation are different. I want them to behave as similarly as possible,
130
+ # but maybe it's okay to copy a directory's contents to an existing directory, but not okay to turn that existing directory
131
+ # into a symlink.
132
+ # Assume file = "~/testdir/file.txt"
133
+ #
134
+ # file.cp( "copy.txt" ) => Copies into the same directory.
135
+ # file.cp( "../pouncer_of_destiny.epubforge") (an existing directory, despite the .ext) ==> copies into ../pouncer_of_destiny.epubforge/file.txt
136
+ # file.cp( "~/tmp/32fb768cd" ) (dest doesn't exist)
137
+ #
138
+ # Assume dir = "~/testdir/" (existing directory with file.txt in it)
139
+ # dir.cp( "")
140
+ end
104
141
  end
105
142
  end
@@ -207,7 +207,7 @@ class TestFilePath < FunWith::Files::TestCase
207
207
  @read_only_file = "/bin/bash".fwf_filepath # This usually exists. I don't know enough Windows to think of a similarly ubiquitous file.
208
208
 
209
209
  if @read_only_file.file? && ! @read_only_file.writable?
210
- assert_raises( Errno::EACCES ) do
210
+ assert_raises( Errno::EPERM ) do
211
211
  @read_only_file.touch
212
212
  end
213
213
  else
@@ -215,4 +215,35 @@ class TestFilePath < FunWith::Files::TestCase
215
215
  end
216
216
  end
217
217
  end
218
+
219
+ context "test ext()" do
220
+ setup do
221
+ @path = "hello.txt".fwf_filepath
222
+ end
223
+
224
+ should "not change path when sent nil as an argument" do
225
+ assert_equal @path.path, @path.ext( nil ).path
226
+ end
227
+
228
+ should "append when given symbol" do
229
+ assert_equal @path.path + ".tgz", @path.ext( :tgz ).path
230
+ end
231
+
232
+ should "append when given string" do
233
+ assert_equal @path.path + ".tgz", @path.ext( 'tgz' ).path
234
+ end
235
+
236
+ should "append correctly when given leading ." do
237
+ assert_equal @path.path + ".tgz", @path.ext( '.tgz' ).path
238
+ end
239
+
240
+ should "append multiple extensions as separate args" do
241
+ assert_equal @path.path + ".backup.tar.gz", @path.ext( :backup, "tar", nil, ".gz" ).path
242
+ end
243
+
244
+ should "append multiple extensions as a single string" do
245
+ assert_equal @path.path + ".backup.tar.gz", @path.ext( ".backup.tar.gz" ).path
246
+ end
247
+
248
+ end
218
249
  end
@@ -26,7 +26,7 @@ class TestFunWithFiles < FunWith::Files::TestCase
26
26
  assert_respond_to( FunWith::Files, :root )
27
27
  assert_respond_to( FunWith::Files, :version )
28
28
 
29
- assert_equal "0.0.11", FunWith::Files.version # Gotta change with every point release. Ick.
29
+ assert_equal "0.0.13", FunWith::Files.version # Gotta change with every point release. Ick.
30
30
  end
31
31
  end
32
32
  end
@@ -19,5 +19,8 @@ class TestTouching < FunWith::Files::TestCase
19
19
  link = file.link( file.up.up.up.join( "hash.dat"), :soft => true )
20
20
  assert link.symlink?
21
21
  end
22
+
23
+ should "create a "
24
+
22
25
  end
23
26
  end
metadata CHANGED
@@ -1,108 +1,116 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fun_with_files
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryce Anderson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-13 00:00:00.000000000 Z
11
+ date: 2016-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xdg
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: fun_with_testing
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.0'
41
- description: ! " A more intuitive syntax for performing a variety of file actions.
42
- Examples:\n \"/\".fwf_filepath.join('usr', 'bin', 'bash').touch\n FunWith::Files::FilePath.home(\"Music\").glob(:ext
43
- => \"mp3\", :recurse => true)\n home = FunWith::Files::FilePath.home\n home.touch(
44
- \"Music\", \"CDs\", \"BubbleBoyTechnoRemixxxx2011\", \"01-jiggypalooza.mp3\" )\n
45
- \ home.touch_dir( \"Music\", \"CDs\", \"ReggaeSmackdown2008\" ) do |dir|\n dir.touch(
46
- \"liner_notes.txt\" )\n dir.touch( \"cover.jpg\" )\n dir.touch( \"01-tokin_by_the_sea.mp3\"
47
- )\n dir.touch( \"02-tourists_be_crazy_mon.mp3\" )\n end\n"
41
+ description: |2
42
+ A more intuitive syntax for performing a variety of file actions. Examples:
43
+ "/".fwf_filepath.join('usr', 'bin', 'bash').touch
44
+ FunWith::Files::FilePath.home("Music").glob(:ext => "mp3", :recurse => true)
45
+ home = FunWith::Files::FilePath.home
46
+ home.touch( "Music", "CDs", "BubbleBoyTechnoRemixxxx2011", "01-jiggypalooza.mp3" )
47
+ home.touch_dir( "Music", "CDs", "ReggaeSmackdown2008" ) do |dir|
48
+ dir.touch( "liner_notes.txt" )
49
+ dir.touch( "cover.jpg" )
50
+ dir.touch( "01-tokin_by_the_sea.mp3" )
51
+ dir.touch( "02-tourists_be_crazy_mon.mp3" )
52
+ end
48
53
  email: keeputahweird@gmail.com
49
54
  executables: []
50
55
  extensions: []
51
56
  extra_rdoc_files:
57
+ - CHANGELOG.markdown
52
58
  - LICENSE.txt
53
59
  - README.rdoc
54
60
  files:
55
- - ./lib/fun_with/files/core_extensions/array.rb
56
- - ./lib/fun_with/files/core_extensions/false.rb
57
- - ./lib/fun_with/files/core_extensions/hash.rb
58
- - ./lib/fun_with/files/core_extensions/nil.rb
59
- - ./lib/fun_with/files/core_extensions/object.rb
60
- - ./lib/fun_with/files/core_extensions/string.rb
61
- - ./lib/fun_with/files/digest_methods.rb
62
- - ./lib/fun_with/files/directory_builder.rb
63
- - ./lib/fun_with/files/downloader.rb
64
- - ./lib/fun_with/files/errors.rb
65
- - ./lib/fun_with/files/file_manipulation_methods.rb
66
- - ./lib/fun_with/files/file_orderer.rb
67
- - ./lib/fun_with/files/file_path.rb
68
- - ./lib/fun_with/files/file_path_class_methods.rb
69
- - ./lib/fun_with/files/file_permission_methods.rb
70
- - ./lib/fun_with/files/file_requirements.rb
71
- - ./lib/fun_with/files/gem_api.rb
72
- - ./lib/fun_with/files/pathname_extensions.rb
73
- - ./lib/fun_with/files/remote_path.rb
74
- - ./lib/fun_with/files/root_path.rb
75
- - ./lib/fun_with/files/string_behavior.rb
76
- - ./lib/fun_with/files/string_extensions.rb
77
- - ./lib/fun_with/files/xdg_extensions.rb
78
- - ./lib/fun_with_files.rb
79
- - ./test/data/empty.txt
80
- - ./test/data/grep1.txt
81
- - ./test/data/grep2.txt
82
- - ./test/data/gsub1.txt
83
- - ./test/data/gsub2.txt
84
- - ./test/helper.rb
85
- - ./test/loadable_dir/dir1/file1.rb
86
- - ./test/loadable_dir/dir2/file2.rb
87
- - ./test/loadable_dir/dir3/file3.rb
88
- - ./test/loadable_dir/dir4/file4.rb
89
- - ./test/loadable_dir/dir5/a.rb
90
- - ./test/loadable_dir/dir5/b.rb
91
- - ./test/loadable_dir/dir5/c.rb
92
- - ./test/loadable_dir/dir5/d.rb
93
- - ./test/test_copying.rb
94
- - ./test/test_core_extensions.rb
95
- - ./test/test_descent.rb
96
- - ./test/test_digest.rb
97
- - ./test/test_directory_builder.rb
98
- - ./test/test_file_manipulation.rb
99
- - ./test/test_file_path.rb
100
- - ./test/test_fun_with_files.rb
101
- - ./test/test_globbing.rb
102
- - ./test/test_loading.rb
103
- - ./test/test_root_path.rb
104
- - ./test/test_symlinking.rb
105
- - ./test/test_touching.rb
61
+ - "./lib/fun_with/files/core_extensions/array.rb"
62
+ - "./lib/fun_with/files/core_extensions/false.rb"
63
+ - "./lib/fun_with/files/core_extensions/file.rb"
64
+ - "./lib/fun_with/files/core_extensions/hash.rb"
65
+ - "./lib/fun_with/files/core_extensions/nil.rb"
66
+ - "./lib/fun_with/files/core_extensions/object.rb"
67
+ - "./lib/fun_with/files/core_extensions/string.rb"
68
+ - "./lib/fun_with/files/digest_methods.rb"
69
+ - "./lib/fun_with/files/directory_builder.rb"
70
+ - "./lib/fun_with/files/downloader.rb"
71
+ - "./lib/fun_with/files/errors.rb"
72
+ - "./lib/fun_with/files/file_manipulation_methods.rb"
73
+ - "./lib/fun_with/files/file_orderer.rb"
74
+ - "./lib/fun_with/files/file_path.rb"
75
+ - "./lib/fun_with/files/file_path_class_methods.rb"
76
+ - "./lib/fun_with/files/file_permission_methods.rb"
77
+ - "./lib/fun_with/files/file_requirements.rb"
78
+ - "./lib/fun_with/files/gem_api.rb"
79
+ - "./lib/fun_with/files/pathname_extensions.rb"
80
+ - "./lib/fun_with/files/remote_path.rb"
81
+ - "./lib/fun_with/files/root_path.rb"
82
+ - "./lib/fun_with/files/string_behavior.rb"
83
+ - "./lib/fun_with/files/string_extensions.rb"
84
+ - "./lib/fun_with/files/xdg_extensions.rb"
85
+ - "./lib/fun_with_files.rb"
86
+ - "./test/data/empty.txt"
87
+ - "./test/data/grep1.txt"
88
+ - "./test/data/grep2.txt"
89
+ - "./test/data/gsub1.txt"
90
+ - "./test/data/gsub2.txt"
91
+ - "./test/helper.rb"
92
+ - "./test/loadable_dir/dir1/file1.rb"
93
+ - "./test/loadable_dir/dir2/file2.rb"
94
+ - "./test/loadable_dir/dir3/file3.rb"
95
+ - "./test/loadable_dir/dir4/file4.rb"
96
+ - "./test/loadable_dir/dir5/a.rb"
97
+ - "./test/loadable_dir/dir5/b.rb"
98
+ - "./test/loadable_dir/dir5/c.rb"
99
+ - "./test/loadable_dir/dir5/d.rb"
100
+ - "./test/test_copying.rb"
101
+ - "./test/test_core_extensions.rb"
102
+ - "./test/test_descent.rb"
103
+ - "./test/test_digest.rb"
104
+ - "./test/test_directory_builder.rb"
105
+ - "./test/test_file_manipulation.rb"
106
+ - "./test/test_file_path.rb"
107
+ - "./test/test_fun_with_files.rb"
108
+ - "./test/test_globbing.rb"
109
+ - "./test/test_loading.rb"
110
+ - "./test/test_root_path.rb"
111
+ - "./test/test_symlinking.rb"
112
+ - "./test/test_touching.rb"
113
+ - CHANGELOG.markdown
106
114
  - Gemfile
107
115
  - LICENSE.txt
108
116
  - README.rdoc
@@ -118,17 +126,17 @@ require_paths:
118
126
  - lib
119
127
  required_ruby_version: !ruby/object:Gem::Requirement
120
128
  requirements:
121
- - - ! '>='
129
+ - - ">="
122
130
  - !ruby/object:Gem::Version
123
131
  version: '0'
124
132
  required_rubygems_version: !ruby/object:Gem::Requirement
125
133
  requirements:
126
- - - ! '>='
134
+ - - ">="
127
135
  - !ruby/object:Gem::Version
128
136
  version: '0'
129
137
  requirements: []
130
138
  rubyforge_project:
131
- rubygems_version: 2.2.2
139
+ rubygems_version: 2.4.6
132
140
  signing_key:
133
141
  specification_version: 4
134
142
  summary: A mashup of several File, FileUtils, and Dir class functions, with a peppy,