fun_with_files 0.0.13 → 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
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,