Class: FunWith::Files::FilePath

Inherits:
Pathname
  • Object
show all
Extended by:
FilePathClassMethods
Defined in:
lib/fun_with/files/file_path.rb

Direct Known Subclasses

RemotePath

Constant Summary collapse

SUCC_DIGIT_COUNT =
6
DEFAULT_TIMESTAMP_FORMAT =
"%Y%m%d%H%M%S%L"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from FilePathClassMethods

config_dir, cwd, data_dir, home, pwd, root

Constructor Details

#initialize(*args) ⇒ FilePath

Returns a new instance of FilePath.



7
8
9
# File 'lib/fun_with/files/file_path.rb', line 7

def initialize( *args )
  super( File.join( *args ) )
end

Instance Attribute Details

#pathObject

Returns the value of attribute path.



11
12
13
# File 'lib/fun_with/files/file_path.rb', line 11

def path
  @path
end

Class Method Details

.tmpdir(&block) ⇒ Object

If block given, temporary directory is deleted at the end of the block, and the value given by the block is returned.

If no block given, the path to the temp directory is returned as a FilePath. Don’t forget to delete it when you’re done.



18
19
20
21
22
23
24
25
26
# File 'lib/fun_with/files/file_path.rb', line 18

def self.tmpdir( &block )
  if block_given?
    Dir.mktmpdir do |d|
      yield d.fwf_filepath
    end
  else
    Dir.mktmpdir.fwf_filepath
  end
end

.tmpfile(ext = :tmp, &block) ⇒ Object

The file is created within a temporary directory



29
30
31
32
33
34
35
36
37
38
39
# File 'lib/fun_with/files/file_path.rb', line 29

def self.tmpfile( ext = :tmp, &block )
  filename = rand( 2 ** 64 ).to_s(16).fwf_filepath.ext( ext )
  
  if block_given?
    self.tmpdir do |tmp|
      yield tmp.join( filename ).touch
    end
  else
    self.tmpdir.join( filename ).touch
  end
end

Instance Method Details

#/(arg) ⇒ Object



52
53
54
# File 'lib/fun_with/files/file_path.rb', line 52

def / arg
  self.join( arg )
end

#[](*args) ⇒ Object



56
57
58
# File 'lib/fun_with/files/file_path.rb', line 56

def [] *args
  self.join(*args)
end

#append(content = nil, &block) ⇒ Object Also known as: <<



280
281
282
283
284
285
286
287
# File 'lib/fun_with/files/file_path.rb', line 280

def append( content = nil, &block )
  File.open( self, "a" ) do |f|
    f << content if content
    if block_given?
      yield f
    end
  end
end

#ascend(&block) ⇒ Object



648
649
650
651
652
653
654
655
656
657
# File 'lib/fun_with/files/file_path.rb', line 648

def ascend( &block )
  path = self.clone

  if path.root?
    yield path
  else
    yield self
    self.up.ascend( &block )
  end
end

#basename_and_extObject

base, ext = @path.basename_and_ext



420
421
422
# File 'lib/fun_with/files/file_path.rb', line 420

def basename_and_ext
  [self.basename_no_ext, self.ext]
end

#basename_no_extObject

Does not return a filepath

TODO: Why not?



321
322
323
# File 'lib/fun_with/files/file_path.rb', line 321

def basename_no_ext
  self.basename.to_s.split(".")[0..-2].join(".")
end

#descend(&block) ⇒ Object



637
638
639
640
641
642
643
644
645
646
# File 'lib/fun_with/files/file_path.rb', line 637

def descend( &block )
  path = self.clone

  if path.root?
    yield path
  else
    self.up.descend( &block )
    yield self
  end
end

#directoryObject

if it’s a file, returns the immediate parent directory. if it’s not a file, returns itself



437
438
439
# File 'lib/fun_with/files/file_path.rb', line 437

def directory
  self.directory? ? self : self.dirname
end

#dirname_and_basenameObject



426
427
428
429
# File 'lib/fun_with/files/file_path.rb', line 426

def dirname_and_basename
  warn("FilePath#dirname_and_basename() is deprecated.  Pathname#split() already existed, and should be used instead.")
  [self.dirname, self.basename]
end

#dirname_and_basename_and_extObject



431
432
433
# File 'lib/fun_with/files/file_path.rb', line 431

def dirname_and_basename_and_ext
  [self.dirname, self.basename_no_ext, self.ext]
end

#doesnt_exist?Boolean Also known as: absent?

Returns:

  • (Boolean)


61
62
63
# File 'lib/fun_with/files/file_path.rb', line 61

def doesnt_exist?
  self.exist? == false
end

#empty?Boolean

empty? has different meanings depending on whether you’re talking about a file or a directory. A directory must not have any files or subdirectories. A file must not have any data in it.

Returns:

  • (Boolean)

Raises:

  • (Exceptions::FileDoesNotExist)


308
309
310
311
312
313
314
315
316
# File 'lib/fun_with/files/file_path.rb', line 308

def empty?
  raise Exceptions::FileDoesNotExist unless self.exist?

  if self.file?
    File.size( self ) == 0
  elsif self.directory?
    self.glob( :all ).fwf_blank?
  end
end

#entriesObject



196
197
198
# File 'lib/fun_with/files/file_path.rb', line 196

def entries
  self.glob( :recurse => false )
end

#expandObject



200
201
202
# File 'lib/fun_with/files/file_path.rb', line 200

def expand
  self.class.new( File.expand_path( self ) )
end

#ext(*args) ⇒ Object

Two separate modes. With no arguments given, returns the current extension as a string (not a filepath) With an argument, returns the path with a .(arg) tacked onto the end. The leading period is wholly optional. Does not return a filepath. Does not include leading period



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/fun_with/files/file_path.rb', line 403

def ext( *args )
  if args.length == 0
    split_basename = self.basename.to_s.split(".")
    split_basename.length > 1 ? split_basename.last : ""
  else
    
    append_to_path = args.compact.map{ |ex|
      ex.to_s.gsub( /^\./, '' )
    }.compact.join( "." )
    
    appended_to_path = "." + "#{append_to_path}" unless append_to_path.fwf_blank?
    
    self.class.new( "#{@path}#{appended_to_path}" )
  end
end

#fwf_filepathObject



455
456
457
# File 'lib/fun_with/files/file_path.rb', line 455

def fwf_filepath
  self
end

#glob(*args, &block) ⇒ Object

opts:

:flags  =>  File::FNM_CASEFOLD
            File::FNM_DOTMATCH
            File::FNM_NOESCAPE
            File::FNM_PATHNAME
            File::FNM_SYSCASE
            See Dir documentation for details.
              Can be given as an integer: (File::FNM_DOTMATCH | File::FNM_NOESCAPE)
              or as an array: [File::FNM_CASEFOLD, File::FNM_DOTMATCH]

:class  =>  [self.class] The class of objects you want returned (String, FilePath, etc.)
            Should probably be a subclass of FilePath or String.  Class.initialize() must accept a string
            [representing a file path] as the sole argument.

:recurse => [defaults true]
:recursive (synonym for :recurse)

:ext => []  A single symbol, or a list containing strings/symbols representing file name extensions.
            No leading periods kthxbai.
:sensitive => true : do a case sensitive search.  I guess the default is an insensitive search, so
                     the default behaves similarly on Windows and Unix.  Not gonna fight it.

:dots => true      : include dotfiles.  Does not include . and ..s unless you also
                     specify the option :parent_and_current => true.

If opts[:recurse] / opts[:ext] not given, the user can get the same
results explicitly with arguments like .glob("**", "*.rb")

:all : if :all is the only argument, this is the same as .glob(“**”, “*”)

Examples: @path.glob( “css”, “*.css” ) # Picks up all css files in the css folder @path.glob( “css”, :ext => :css ) # same @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. @path.glob(“**”, “*”) # same TODO: depth argument? depth should override recurse. When extention given, recursion should default to true?

the find -depth argument says depth(0) is the root of the searched directory, any files beneath would be depth(1)


121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/fun_with/files/file_path.rb', line 121

def glob( *args, &block )
  args.push( :all ) if args.fwf_blank?
  opts = args.last.is_a?(Hash) ? args.pop : {}

  if args.last == :all
    all_arg_given = true
    args.pop
  else
    all_arg_given = false
  end

  flags = case (flags_given = opts.delete(:flags))
          when NilClass
            0
          when Array      # should be an array of integers or File::FNM_<FLAGNAME>s
            flags_given.inject(0) do |memo, obj|
              memo | obj
            end
          when Integer
            flags_given
          end

  flags |= File::FNM_DOTMATCH if opts[:dots]
  flags |= File::FNM_CASEFOLD if opts[:sensitive]   # case sensitive.  Only applies to Windows.

  recurse = if all_arg_given
              if opts[:recursive] == false || opts[:recurse] == false
                false
              else
                true
              end
            else
              opts[:recursive] == true || opts[:recurse] == true || false
            end

  if all_arg_given
    if recurse
      args = ["**", "*"]
    else
      args = ["*"]
    end
  else
    args.push("**") if recurse

    extensions = case opts[:ext]
    when Symbol, String
      "*.#{opts[:ext]}"
    when Array
      extensions = opts[:ext].map(&:to_s).join(',')
      "*.{#{extensions}}"                            # The Dir.glob format for this is '.{ext1,ext2,ext3}'
    when NilClass
      if args.fwf_blank?
        "*"
      else
        nil
      end
    end

    args.push( extensions ) if extensions
  end

  class_to_return = opts[:class] || self.class

  files = Dir.glob( self.join(*args), flags ).map{ |f| class_to_return.new( f ) }
  files.reject!{ |f| f.basename.to_s.match( /^\.\.?$/ ) } unless opts[:parent_and_current]

  if block_given?
    for file in files
      yield file
    end
  else
    files
  end
end

#grep(regex, &block) ⇒ Object

Returns a [list] of the lines in the file matching the given file. Contrast with



293
294
295
296
297
298
299
300
301
302
303
# File 'lib/fun_with/files/file_path.rb', line 293

def grep( regex, &block )
  return [] unless self.file?
  matching = []
  self.each_line do |line|
    matching.push( line ) if line.match( regex )
    yield line if block_given?
  end


  matching
end

#join(*args) {|joined_path| ... } ⇒ Object Also known as: down

Yields:

  • (joined_path)


41
42
43
44
45
# File 'lib/fun_with/files/file_path.rb', line 41

def join( *args, &block )
  joined_path = self.class.new( super( *(args.map(&:to_s) ) ) )
  yield joined_path if block_given?
  joined_path
end

#join!(*args, &block) ⇒ Object



47
48
49
50
# File 'lib/fun_with/files/file_path.rb', line 47

def join!( *args, &block )
  @path = self.join( *args, &block ).to_str
  self
end

#loadObject

TODO: succ_last : find the last existing file of the given sequence. TODO: succ_next : find the first free file of the given sequence



580
581
582
583
584
585
586
# File 'lib/fun_with/files/file_path.rb', line 580

def load
  if self.directory?
    self.glob( :recursive => true, :ext => "rb" ).map(&:load)
  else
    Kernel.load( self.expand )
  end
end

#not_a_file?Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/fun_with/files/file_path.rb', line 65

def not_a_file?
  ! self.file?
end

#originalObject



445
446
447
# File 'lib/fun_with/files/file_path.rb', line 445

def original
  self.symlink? ? self.readlink.original : self
end

#original?Boolean

Returns:

  • (Boolean)


441
442
443
# File 'lib/fun_with/files/file_path.rb', line 441

def original?
  !self.symlink?
end

#relative_path_from(dir) ⇒ Object

Basically Pathname.relative_path_from, but you can pass in strings



450
451
452
453
# File 'lib/fun_with/files/file_path.rb', line 450

def relative_path_from( dir )
  dir = super( Pathname.new( dir ) )
  self.class.new( dir )
end

#requirObject

Require ALL THE RUBY! This may be a bad idea…

Sometimes it fails to require a file because one of the necessary prerequisites hasn’t been required yet (NameError). requir catches this failure and stores the failed requirement in order to try it later. Doesn’t fail until it goes through a full loop where none of the required files were successful.



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/fun_with/files/file_path.rb', line 595

def requir
  if self.directory?
    requirements = self.glob( :recursive => true, :ext => "rb" )
    successfully_required = 1337  # need to break into initial loop
    failed_requirements = []
    error_messages = []

    while requirements.length > 0 && successfully_required > 0
      successfully_required = 0
      failed_requirements = []
      error_messages = []

      for requirement in requirements
        begin
          requirement.requir
          successfully_required += 1
        rescue Exception => e
          failed_requirements << requirement
          error_messages << "Error while requiring #{requirement} : #{e.message} (#{e.class})"
        end
      end

      requirements = failed_requirements
    end

    if failed_requirements.length > 0
      msg = "requiring directory #{self} failed:\n"
      for message in error_messages
        msg << "\n\terror message: #{message}"
      end

      raise NameError.new(msg)
    end
  else
    require self.expand.gsub( /\.rb$/, '' )
  end
end

#root?Boolean

Returns:

  • (Boolean)


633
634
635
# File 'lib/fun_with/files/file_path.rb', line 633

def root?
  self == self.up
end

#separatorObject

TODO : Not working as intended. def separator( s = nil )

# If s is nil, then we're asking for the separator
if s.nil?
  @separator || File::SEPARATOR
else
  @separator = s
end
# otherwise we're installing a separator

end



673
674
675
# File 'lib/fun_with/files/file_path.rb', line 673

def separator
  File::SEPARATOR
end

#specifier(str) ⇒ Object

puts a string between the main part of the basename and the extension or after the basename if there is no extension. Used to describe some file variant. Example “/home/docs/my_awesome_screenplay.txt”.fwf_filepath.specifier(“final_draft”)

=> FunWith::Files::FilePath:/home/docs/my_awesome_screenplay.final_draft.txt

Oh hush. I find it useful.



526
527
528
529
530
531
532
533
534
535
536
537
# File 'lib/fun_with/files/file_path.rb', line 526

def specifier( str )
  str = str.to_s
  chunks = self.to_s.split(".")

  if chunks.length == 1
    chunks << str
  else
    chunks = chunks[0..-2] + [str] + [chunks[-1]]
  end

  chunks.join(".").fwf_filepath
end

#succ(opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false }) ⇒ Object

Gives a sequence of files. Examples: file.dat –> file.000000.dat file_without_ext –> file_without_ext.000000 If it sees a six-digit number at or near the end of the filename, it increments it.

You can change the length of the sequence string by passing in an argument, but it should always be the same value for a given set of files.

TODO: Need to get this relying on the specifier() method.



470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/fun_with/files/file_path.rb', line 470

def succ( opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false } )
  if timestamp = opts[:timestamp]
    timestamp_format = timestamp.is_a?(String) ? timestamp : DEFAULT_TIMESTAMP_FORMAT
    timestamp = Time.now.strftime( timestamp_format )
    digit_count = timestamp.length
  else
    timestamp = false
    digit_count = opts[:digit_count]
  end

  chunks = self.basename.to_s.split(".")
  # not yet sequence stamped, no file extension.
  if chunks.length == 1
    if timestamp
      chunks.push( timestamp )
    else
      chunks.push( "0" * digit_count )
    end
  # sequence stamp before file extension
  elsif match_data = chunks[-2].match( /^(\d{#{digit_count}})$/ )
    if timestamp
      chunks[-2] = timestamp
    else
      i = match_data[1].to_i + 1
      chunks[-2] = sprintf("%0#{digit_count}i", i)
    end
  # try to match sequence stamp to end of filename
  elsif match_data = chunks[-1].match( /^(\d{#{digit_count}})$/ )
    if timestamp
      chunks[-1] = timestamp
    else
      i = match_data[1].to_i + 1
      chunks[-1] = sprintf("%0#{digit_count}i", i)
    end
  # not yet sequence_stamped, has file extension
  else
    chunks = [chunks[0..-2], (timestamp ? timestamp : "0" * digit_count), chunks[-1]].flatten
  end

  self.up.join( chunks.join(".") )
end

#succession(opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false }) ⇒ Object

TODO: succession : enumerates a sequence of files that get passed to a block in order.



541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/fun_with/files/file_path.rb', line 541

def succession( opts = { digit_count: SUCC_DIGIT_COUNT, timestamp: false } )
  if opts[:timestamp]
    opts[:timestamp_format] ||= "%Y%m%d%H%M%S%L"
    timestamp = Time.now.strftime( opts[:timestamp_format] )
    digit_count = timestamp.length
  else
    timestamp = false
    digit_count = opts[:digit_count]
  end

  chunks = self.basename.to_s.split(".")
  glob_stamp_matcher = '[0-9]' * digit_count

  # unstamped filename, no extension
  if chunks.length == 1
    original = chunks.first
    stamped = [original, glob_stamp_matcher].join(".")
  # stamped filename, no extension
  elsif chunks[-1].match( /^\d{#{digit_count}}$/ )
    original = chunks[0..-2].join(".")
    stamped = [original, glob_stamp_matcher].join(".")
  # stamped filename, has extension
  elsif chunks[-2].match( /^\d{#{digit_count}}$/ )
    original = [chunks[0..-3], chunks.last].flatten.join(".")
    stamped = [chunks[0..-3], glob_stamp_matcher, chunks.last].join(".")
  # unstamped filename, has extension
  else
    original = chunks.join(".")
    stamped = [ chunks[0..-2], glob_stamp_matcher, chunks[-1] ].flatten.join(".")
  end

  [self.dirname.join(original), self.dirname.glob(stamped)].flatten
end

#timestamp(format = true) {|nxt| ... } ⇒ Object

Yields:

  • (nxt)


513
514
515
516
517
# File 'lib/fun_with/files/file_path.rb', line 513

def timestamp( format = true, &block )
  nxt = self.succ( :timestamp => format )
  yield nxt if block_given?
  nxt
end

#to_pathnameObject



659
660
661
# File 'lib/fun_with/files/file_path.rb', line 659

def to_pathname
  Pathname.new( @path )
end

#touch(*args) {|touched| ... } ⇒ Object

Raises error if self is a file and args present. Raises error if the file is not accessible for writing, or cannot be created. attempts to create a directory

Takes an options hash as the last argument, allowing same options as FileUtils.touch

Yields:

  • (touched)


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/fun_with/files/file_path.rb', line 209

def touch( *args, &block )
  args, opts = extract_opts_from_args( args )

  raise "Cannot create subdirectory to a file" if self.file? && args.length > 0
  touched = self.join(*args)

  dir_for_touched_file = case args.length
    when 0
      self.up
    when 1
      self
    when 2..Float::INFINITY
      self.join( *(args[0..-2] ) )
    end

  self.touch_dir( dir_for_touched_file, opts ) unless dir_for_touched_file.directory?
  FileUtils.touch( touched, narrow_options( opts, FileUtils::OPT_TABLE["touch"] ) )

  yield touched if block_given?
  return touched
end

#touch_dir(*args) {|touched| ... } ⇒ Object

Takes the options of both FileUtils.touch and FileUtils.mkdir_p mkdir_p options will only matter if the directory is being created.

Yields:

  • (touched)


233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/fun_with/files/file_path.rb', line 233

def touch_dir( *args, &block )
  args, opts = extract_opts_from_args( args )

  touched = self.join(*args)
  if touched.directory?
    FileUtils.touch( touched, narrow_options( opts, FileUtils::OPT_TABLE["touch"] ) )    # update access time
  else
    FileUtils.mkdir_p( touched, narrow_options( opts, FileUtils::OPT_TABLE["mkdir_p"] ) )  # create directory (and any needed parents)
  end

  yield touched if block_given?
  return touched
end

#upObject

If called on a file instead of a directory, has the same effect as path.dirname



75
76
77
# File 'lib/fun_with/files/file_path.rb', line 75

def up
  self.class.new( self.join("..") ).expand
end

#without_ext(ext_val = nil) ⇒ Object

Returns the path, stripped of the final extension (.ARG). The result cannot destroy a dotfile or leave an empty path

"~/.bashrc".without_ext( .bashrc )  => "~/.bashrc",

if an argument is given, the final ext must match the given argument, or a copy of the unaltered path is returned.

Also:

Don't add a leading ./ when the original didn't have one
Case InSeNSItive, because I can't think of a use case for "only"
   strip the extension if the capitalization matches
Chews up any number of leading '.'s
For the moment,


339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/fun_with/files/file_path.rb', line 339

def without_ext( ext_val = nil )
  ext_chopper_regex = if ext_val.fwf_blank?
                        /\.+\w+$/i             # any ending "word characters"
                      else
                        # It's okay for the caller to make the leading period explicit
                        ext_val = ext_val.to_s.gsub( /^\./, '' )
                        /\.+#{ext_val}+$/i
                      end

  chopped_str = @path.gsub( ext_chopper_regex, "" )
  
  do_we_chop = if chopped_str == @path   # no change, then sure, why not?
                 true
               elsif chopped_str.fwf_blank? || chopped_str[-1] == self.separator() || chopped_str[-1] == "."
                 false
               else
                 true
               end
               
  self.class.new( do_we_chop ? chopped_str : @path )
  
  # # If the results fail to live up to some pre-defined
  #
  #
  #
  # # do we or don't we?
  # chop_extension = true
  #
  # #   Don't if there's an extension mismatch
  # #   Don't if the remainder is a /. (original was a dot_file)
  #
  #
  #
  #
  #
  #
  # _dirname, _basename, _ext = self.dirname_and_basename_and_ext
  # debugger if _basename =~ "hello"
  # ext_val = ext_val.to_s
  #
  # # 1) Only perform if the extension match the one given (or was a blank ext given?)
  #
  #
  #
  # new_path = @path.clone
  #
  # current_ext = self.ext
  #
  # e = e.to_s
  #
  # if e.fwf_present?
  #   new_path.gsub!( /\.#{e}$/, "" )
  #   result = self.gsub(/#{ not_beginning_of_line_or_separator}\.#{self.ext}$/, '')
  # else
  #   self.clone
  # end
  #
  # self.class.new( new_path )
end

#write(*args, &block) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/fun_with/files/file_path.rb', line 247

def write( *args, &block )
  args, opts = extract_opts_from_args( args )

  content = args.first

  if content == :random
    self.write_random_data( opts )
  else
    File.open( self, "w" ) do |f|
      f << content if content
      if block_given?
        yield f
      end
    end
  end
end

#write_random_data(sz, opts = {}) ⇒ Object

sz: number of bytes to write to the file opts => :overwrite or :append seed: What number to seed the random number generator with

FUTURE: May perform slowly on large sz inputs?



269
270
271
272
273
274
275
276
277
278
# File 'lib/fun_with/files/file_path.rb', line 269

def write_random_data( sz, opts = {} )
  rng = Random.new( opts[:seed] || Random::new_seed )
  mode = opts[:mode] || :overwrite

  if mode == :overwrite
    self.write( rng.bytes( sz ) )
  elsif mode == :append
    self.append( rng.bytes( sz ) )
  end
end