fun_with_files 0.0.15 → 0.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.markdown +15 -3
  3. data/Gemfile +17 -7
  4. data/{README.rdoc → README.markdown} +11 -10
  5. data/VERSION +1 -1
  6. data/lib/fun_with/files/bootstrapper.rb +87 -0
  7. data/lib/fun_with/files/digest_methods.rb +30 -16
  8. data/lib/fun_with/files/directory_builder.rb +4 -0
  9. data/lib/fun_with/files/downloader.rb +3 -19
  10. data/lib/fun_with/files/errors.rb +9 -1
  11. data/lib/fun_with/files/file_manipulation_methods.rb +25 -15
  12. data/lib/fun_with/files/file_path.rb +147 -150
  13. data/lib/fun_with/files/file_path_class_methods.rb +23 -2
  14. data/lib/fun_with/files/file_permission_methods.rb +18 -7
  15. data/lib/fun_with/files/file_requirements.rb +63 -7
  16. data/lib/fun_with/files/requirements/manager.rb +104 -0
  17. data/lib/fun_with/files/root_path.rb +3 -3
  18. data/lib/fun_with/files/stat_methods.rb +33 -0
  19. data/lib/fun_with/files/string_behavior.rb +6 -2
  20. data/lib/fun_with/files/utils/byte_size.rb +143 -0
  21. data/lib/fun_with/files/utils/opts.rb +26 -0
  22. data/lib/fun_with/files/utils/succession.rb +47 -0
  23. data/lib/fun_with/files/utils/timestamp.rb +47 -0
  24. data/lib/fun_with/files/utils/timestamp_format.rb +31 -0
  25. data/lib/fun_with/files/watcher.rb +157 -0
  26. data/lib/fun_with/files/watchers/directory_watcher.rb +67 -0
  27. data/lib/fun_with/files/watchers/file_watcher.rb +45 -0
  28. data/lib/fun_with/files/watchers/missing_watcher.rb +23 -0
  29. data/lib/fun_with/files/watchers/node_watcher.rb +44 -0
  30. data/lib/fun_with/testing/assertions/fun_with_files.rb +91 -0
  31. data/lib/fun_with/testing/test_case_extensions.rb +12 -0
  32. data/lib/fun_with_files.rb +5 -75
  33. data/test/helper.rb +13 -5
  34. data/test/test_core_extensions.rb +5 -0
  35. data/test/test_directory_builder.rb +29 -10
  36. data/test/test_extension_methods.rb +62 -0
  37. data/test/test_file_manipulation.rb +2 -2
  38. data/test/test_file_path.rb +18 -39
  39. data/test/test_file_requirements.rb +36 -0
  40. data/test/test_fun_with_files.rb +1 -1
  41. data/test/test_fwf_assertions.rb +62 -0
  42. data/test/test_moving_files.rb +111 -0
  43. data/test/test_permission_methods.rb +22 -0
  44. data/test/test_root_path.rb +9 -0
  45. data/test/test_stat_methods.rb +17 -0
  46. data/test/test_timestamping.rb +74 -0
  47. data/test/test_utils_bytesize.rb +71 -0
  48. data/test/test_utils_succession.rb +30 -0
  49. data/test/test_watchers.rb +196 -0
  50. metadata +54 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6d1ccc274aea1937c63f81c2fe7897349aadbdc9
4
- data.tar.gz: 5ae174de323cfa4aba42d459b5848416c173bcfa
2
+ SHA256:
3
+ metadata.gz: d22de5c44faf70d934da05efbd93f17373e8317bbc6304c5674c2f79183d45b2
4
+ data.tar.gz: 89d022cd00c4e14be9c82fc62045e82db6095e9a16239e29c085a605c0c0cde9
5
5
  SHA512:
6
- metadata.gz: 8125f7794e66f690c5180e88f6d827463d251878417e1689e2360bac84ad9d9909530a2c299409468558320817763a2d66ac4bf45308e6cacdca26363a63eda4
7
- data.tar.gz: 13c43cb5c978693715ffa1a21c72c07c464eaab0deb4260826b49d120748967ed7fd20c65234888347a2a96ab59529a6517a10c304dc99d9cf0bae33046661c1
6
+ metadata.gz: b8fb4d70f1bc7c29439b7237e38e79f5661334fd7c8c3457f5ae1bde20b053eb95dccd5d8c5ad3bf3a4f16443375ace9f889822116680b136d154e53ab118411
7
+ data.tar.gz: fdd5d8bc41b89c5e0a4f3c83580f4f45679cb3bc38ee9539f332f1a26572cf68ffd549b7118787f6068d1247711cafe175cf7389a0aff1532cf6407c96384ba7
data/CHANGELOG.markdown CHANGED
@@ -1,13 +1,25 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ v0.0.18
5
+ -------
6
+
7
+ * restricted the use of the file.succ() method, and removed timestamp functionality from it. Now it only works if the initial file has an extension (no trailing counter, like file.000001 ). Still fills in
8
+
9
+ v0.0.17
10
+ -------
11
+
12
+ * `.without_ext()` now takes an argument, will only strip that extension.
13
+ * updated `fun_with_testing` dependency, moved FunWith::Files-related stuff from that gem to this one.
14
+
15
+
4
16
  v0.0.14
5
- =======
17
+ -------
6
18
 
7
- Make symlinking actually work in a sensible way.
19
+ ???
8
20
 
9
21
  v0.0.12
10
- =======
22
+ -------
11
23
 
12
24
  FilePath.touch() takes same options as FileUtils.touch()
13
25
  FilePath.glob() now takes a block (yields files one at a time)
data/Gemfile CHANGED
@@ -7,12 +7,22 @@ source "http://rubygems.org"
7
7
  # Include everything needed to run rake, tests, features, etc.
8
8
 
9
9
  group :development do
10
- # gem "shoulda", "~> 3", ">= 3.5"
11
- # gem "rdoc", "~> 3.12"
12
- # gem "bundler", "~> 1.5"
13
- # gem "juwelier", "~> 2.0"
14
- # gem "debugger", "~> 1.6"
15
- gem "fun_with_testing", "~> 0.0", ">= 0.0.5"
10
+ gem "debug"
11
+ gem "fun_with_testing", "~> 0.0", ">= 0.0.7"
16
12
  end
17
13
 
18
- gem "xdg", "~> 2"
14
+
15
+ xdg_version = case RUBY_VERSION
16
+ when /^2\.7/
17
+ 3
18
+ when /^3\.0/
19
+ 5
20
+ when /^3\.2/
21
+ 7
22
+ end
23
+
24
+
25
+
26
+ gem "xdg", "~> #{xdg_version}"
27
+
28
+
@@ -1,4 +1,7 @@
1
- = fun_with_files =
1
+ # `fun_with_files`
2
+
3
+
4
+
2
5
 
3
6
  FunWith::Files adds a bit of whimsy to your file manipulations, if that's what you're looking for.
4
7
 
@@ -36,18 +39,17 @@ To the code!
36
39
 
37
40
 
38
41
 
39
- === Linking files ===
42
+ ### Linking files ###
40
43
 
41
44
  While fwf.symlink and fwf.link are both backed by FileUtils.ln / FileUtils.ln_s, the defaults are somewhat different
42
45
 
43
46
 
44
47
 
45
- == DirectoryBuilder ==
48
+ ## DirectoryBuilder ##
46
49
 
47
50
  DirectoryBuilder is a class for defining and populating a file hierarchy with relative ease. DirectoryBuilder is probably most easily demonstrated by example. Sample code:
48
51
 
49
- # starts by creating directory. If parent
50
- # directories don't exist, they will soon.
52
+ # starts by creating directory. If parent directories don't exist, they will soon.
51
53
  DirectoryBuilder.create( '~/project' ) do |b|
52
54
  b.dir("images") do # creates subdirectory "images", which gets populated within the block
53
55
  for img in src_dir.entries.select{|img| img.extension == ".png"}
@@ -60,7 +62,7 @@ DirectoryBuilder is a class for defining and populating a file hierarchy with re
60
62
 
61
63
  b.dir("text", "scenes") do # creates ~/project/text/scenes subdir (creating two new directories text/ and text/scene/)
62
64
  b.file( "adventure_time.txt" ) do |f|
63
- f << "Fill this in later"
65
+ f << "Fill this in later" # text is written to the file
64
66
  end
65
67
 
66
68
  # calling .file without feeding it a block leaves it open for writing,
@@ -106,7 +108,7 @@ DirectoryBuilder is a class for defining and populating a file hierarchy with re
106
108
 
107
109
 
108
110
 
109
- == Contributing to fun_with_files
111
+ ## Contributing to fun_with_files ##
110
112
 
111
113
  Boilerplate from Juwelier, but seems to make sense.
112
114
 
@@ -118,8 +120,7 @@ Boilerplate from Juwelier, but seems to make sense.
118
120
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
119
121
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
120
122
 
121
- == Copyright
123
+ ## Copyright ##
122
124
 
123
- Copyright (c) 2013 Bryce Anderson. See LICENSE.txt for
124
- further details.
125
+ Copyright (c) 2020 Bryce Anderson. See LICENSE.txt for further details.
125
126
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.15
1
+ 0.0.18
@@ -0,0 +1,87 @@
1
+ module FunWith
2
+ module Files
3
+ class Bootstrapper
4
+ def self.bootstrap
5
+ self.new.bootstrap
6
+ end
7
+
8
+ def bootstrap
9
+ load_core_extensions
10
+ install_minimal_requir_functionality
11
+ run_requir
12
+ rootify
13
+ add_filepath_class_methods
14
+ extend_gem_api
15
+ end
16
+
17
+ protected
18
+ # gets all the core-extending modules from fun_with/files/core_extensions and uses them to
19
+ # beef up the core classes
20
+ def load_core_extensions
21
+ for file in Dir.glob( File.join( __dir__, "core_extensions", "*.rb" ) )
22
+ # remove trailing extension to make it require-friendly.
23
+ file = file.gsub(/\.rb$/,'')
24
+
25
+ require_relative file
26
+
27
+ # convert filename into class name
28
+ target_class_str = filename_to_class_name( file )
29
+
30
+ # get the core class that needs extending, and the
31
+ target_class = Kernel.const_get( target_class_str )
32
+ mixin_class = Kernel.const_get( "FunWith::Files::CoreExtensions::#{target_class_str}" )
33
+
34
+ target_class.send( :include, mixin_class )
35
+ end
36
+ end
37
+
38
+ def install_minimal_requir_functionality
39
+ for fil in %w( file_path
40
+ string_behavior
41
+ file_manipulation_methods
42
+ file_permission_methods
43
+ digest_methods
44
+ file_requirements
45
+ requirements/manager
46
+ stat_methods )
47
+ require_relative fil
48
+ end
49
+
50
+ # These have some FilePath methods required by .requir()
51
+ for mod in [ FunWith::Files::StringBehavior,
52
+ FunWith::Files::FileManipulationMethods,
53
+ FunWith::Files::FilePermissionMethods,
54
+ FunWith::Files::DigestMethods,
55
+ FunWith::Files::FileRequirements,
56
+ FunWith::Files::StatMethods ]
57
+ FunWith::Files::FilePath.send( :include, mod )
58
+ end
59
+ end
60
+
61
+ def run_requir
62
+ lib_dir = __dir__.fwf_filepath.up
63
+
64
+ # And requir() everything else
65
+ lib_dir.requir
66
+ end
67
+
68
+ def rootify
69
+ root_dir = __dir__.fwf_filepath.up.up.up
70
+ FunWith::Files::RootPath.rootify( FunWith::Files, root_dir )
71
+ end
72
+
73
+ def add_filepath_class_methods
74
+ FunWith::Files::FilePath.extend( FunWith::Files::FilePathClassMethods )
75
+ end
76
+
77
+ def extend_gem_api
78
+ FunWith::Files.extend( FunWith::Files::GemAPI )
79
+ end
80
+
81
+
82
+ def filename_to_class_name( str )
83
+ File.basename( str ).split( "_" ).map(&:capitalize).join("")
84
+ end
85
+ end
86
+ end
87
+ end
@@ -3,36 +3,47 @@ module FunWith
3
3
  DIGEST_METHODS = [:md5, :sha1, :sha2, :sha224, :sha256, :sha384, :sha512]
4
4
 
5
5
  module DigestMethods
6
- def md5
7
- digest( Digest::MD5 )
6
+ def md5( bytes = :all, offset = 0 )
7
+ digest( Digest::MD5, bytes, offset )
8
8
  end
9
9
 
10
- def sha1
11
- digest( Digest::SHA1 )
10
+ def sha1( bytes = :all, offset = 0 )
11
+ digest( Digest::SHA1, bytes, offset )
12
12
  end
13
13
 
14
- def sha2
15
- digest( Digest::SHA2 )
14
+ def sha2( bytes = :all, offset = 0 )
15
+ digest( Digest::SHA2, bytes, offset )
16
16
  end
17
17
 
18
- def sha224
19
- digest( Digest::SHA224 )
18
+ def sha224( bytes = :all, offset = 0 )
19
+ digest( Digest::SHA224, bytes, offset )
20
20
  end
21
21
 
22
- def sha256
23
- digest( Digest::SHA256 )
22
+ def sha256( bytes = :all, offset = 0 )
23
+ digest( Digest::SHA256, bytes, offset )
24
24
  end
25
25
 
26
- def sha384
27
- digest( Digest::SHA384 )
26
+ def sha384( bytes = :all, offset = 0 )
27
+ digest( Digest::SHA384, bytes, offset )
28
28
  end
29
29
 
30
- def sha512
31
- digest( Digest::SHA512 )
30
+ def sha512( bytes = :all, offset = 0 )
31
+ digest( Digest::SHA512, bytes, offset )
32
32
  end
33
33
 
34
- def digest( digest_class = Digest::MD5 )
35
- self.file? ? digest_class.hexdigest( self.read ) : ""
34
+ def digest( digest_class = Digest::MD5, bytes = :all, offset = 0 )
35
+ if self.file? && self.readable?
36
+ if bytes == :all
37
+ digest_class.hexdigest( self.read )
38
+ elsif bytes.is_a?( Integer )
39
+ digest_class.hexdigest( self.read( bytes, offset ) )
40
+ else
41
+ raise ArgumentError.new( "FunWith::Files::DigestMethods.digest() error: bytes argument must be an integer or :all")
42
+ end
43
+ else
44
+ raise IOError.new( "Not a file: #{self.path}" ) unless self.file?
45
+ raise IOError.new( "Not readable: #{self.path}" ) unless self.readable?
46
+ end
36
47
  end
37
48
 
38
49
  # Takes any of the above-named digest functions, determines
@@ -43,6 +54,9 @@ module FunWith
43
54
  # TODO: how to get around the :md6 problem? That is, where the
44
55
  # user is sending the wrong key, and hence not getting false back
45
56
  def valid_digest?( opts )
57
+ digest_opts = Utils::Opts.narrow_options( opts, DIGEST_METHODS )
58
+
59
+
46
60
  for method, digest in opts
47
61
  if DIGEST_METHODS.include?( method )
48
62
  return false unless self.send( method ) == digest
@@ -41,6 +41,9 @@ module FunWith
41
41
 
42
42
  # Copies the given source file into a file in the current_path.
43
43
  # If a dest_name is given, the new file will be given that name.
44
+ #
45
+ # TODO: Improve testing, explain behavior better, need a way to distinguish between
46
+ # forceful and gentle copying
44
47
  def copy( src_filepath, dst_name = nil )
45
48
  dst_filepath = dst_name ? @current_path.join( dst_name ) : @current_path
46
49
  FileUtils.copy( src_filepath, dst_filepath )
@@ -96,6 +99,7 @@ module FunWith
96
99
  end
97
100
  end
98
101
 
102
+ # The actual method is installed by 'fun_with_templates'
99
103
  def template( *args )
100
104
  raise "DirectoryBuilder cannot use template() function. require 'fun_with_templates' to enable."
101
105
  end
@@ -22,7 +22,7 @@ module FunWith
22
22
  @uri = URI.parse( url )
23
23
  @io = io
24
24
 
25
- open( url ) do |f|
25
+ URI.open( url ) do |f|
26
26
  @io << f.read
27
27
  end
28
28
 
@@ -40,26 +40,10 @@ module FunWith
40
40
  warn( "File did not download correctly, or was deleted: #{io_path}")
41
41
  false
42
42
  end
43
-
44
- # @io << Net::HTTP.get( @uri )
45
-
46
- # Net::HTTP.start( @uri.host, @uri.port ) do |http|
47
- # http.request_get( @uri.path ) do |request|
48
- # request.read_body do |seg|
49
- # puts "============================== #{seg} ============================="
50
- # io << seg
51
- # #hack -- adjust to suit:
52
- # sleep 0.005
53
- # end
54
- # end
55
- # end
56
-
57
-
58
-
59
-
60
- rescue Exception => e
43
+ rescue StandardError => e
61
44
  handle_network_errors( e )
62
45
  end
46
+
63
47
 
64
48
  def handle_network_errors( e )
65
49
  raise e
@@ -1,5 +1,13 @@
1
1
  module FunWith
2
2
  module Files
3
- class NoSuchFile < Exception; end
3
+ # Useful... why, exactly?
4
+ module Errors
5
+ class Error < StandardError; end
6
+ class SuccessionFormattingError < Error; end
7
+ class TimestampFormatUnrecognized < Error; end
8
+ class FileNotEmpty < Error; end
9
+ class NotADirectory < Error; end
10
+ class NotAFile < Error; end
11
+ end
4
12
  end
5
13
  end
@@ -15,6 +15,12 @@ module FunWith
15
15
  # ln_s(list, destdir, options)
16
16
  # ln_sf(src, dest, options)
17
17
 
18
+ #
19
+
20
+
21
+ # => [:alias_method, :ancestors, :attr, :attr_accessor, :attr_reader, :attr_writer, :autoload, :autoload?, :cd, :chdir, :chmod, :chmod_R, :chown, :chown_R, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :cmp, :collect_method, :commands, :compare_file, :compare_stream, :const_defined?, :const_get, :const_missing, :const_set, :constants, :copy, :copy_entry, :copy_file, :copy_stream, :cp, :cp_r, :define_method, :deprecate_constant, :getwd, :have_option?, :identical?, :include, :include?, :included_modules, :install, :instance_method, :instance_methods, :link, :ln, :ln_s, :ln_sf, :makedirs, :method_defined?, :mkdir, :mkdir_p, :mkpath, :module_eval, :module_exec, :move, :mv, :name, :options, :options_of, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_module_function, :protected_instance_methods, :protected_method_defined?, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method_defined?, :pwd, :remove, :remove_class_variable, :remove_dir, :remove_entry, :remove_entry_secure, :remove_file, :remove_method, :rm, :rm_f, :rm_r, :rm_rf, :rmdir, :rmtree, :safe_unlink, :singleton_class?, :symlink, :touch, :undef_method, :uptodate?]
22
+
23
+
18
24
  # opts are the last argument, and are passed to FileUtils.cp_r
19
25
  # returns the destination path.
20
26
  # How to detect failure? What to return on failure?
@@ -22,16 +28,22 @@ module FunWith
22
28
  #
23
29
  def cp( *args )
24
30
  destination_and_options( args ) do |dest, opts|
25
- FileUtils.cp_r( self, dest, narrow_options( opts, FileUtils::OPT_TABLE["cp_r"] ) )
31
+ FileUtils.cp_r( self, dest, ** Utils::Opts.narrow_file_utils_options( opts, :cp_r ) )
26
32
  dest.fwf_filepath
27
33
  end
28
34
  end
29
35
 
30
36
  alias :copy :cp
31
37
 
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
33
- def mv( *args )
34
-
38
+ # Treat as a copy then a delete? Nah, that's a lot slower especially for larger files. Should be much more in tune with what the command line program does.
39
+ # Treat it as syntactic sugar for FileUtils.mv?
40
+ # Also want to update the path to the new location - not implemented yet
41
+ #
42
+ #
43
+ def mv( dst, options = {} )
44
+ # what does FileUtils.rm actually return? Glancing an the source, it
45
+ # seems to only throw errors.
46
+ FileUtils.mv( self, dst, **options )
35
47
  end
36
48
 
37
49
  alias :move :mv
@@ -45,13 +57,11 @@ module FunWith
45
57
  def link *args
46
58
  self.destination_and_options( args ) do |lnk, opts|
47
59
  symlink_requested = self.directory? || opts[:symbolic] || opts[:sym] || opts[:soft]
48
-
60
+
49
61
  if symlink_requested
50
62
  self.symlink lnk, opts
51
63
  else
52
- opts = narrow_options opts, FileUtils::OPT_TABLE["ln"]
53
-
54
- FileUtils.ln self, lnk, opts
64
+ FileUtils.ln self, lnk, ** Utils::Opts.narrow_file_utils_options( opts, :ln )
55
65
  end
56
66
 
57
67
  lnk.fwf_filepath
@@ -78,7 +88,7 @@ module FunWith
78
88
  lnk = lnk.fwf_filepath
79
89
  end
80
90
 
81
- FileUtils.ln_s( self, lnk, narrow_options( opts, FileUtils::OPT_TABLE["ln_s"] ) )
91
+ FileUtils.ln_s( self, lnk, ** Utils::Opts.narrow_file_utils_options( opts, :ln_s ) )
82
92
  lnk.fwf_filepath
83
93
  end
84
94
 
@@ -86,7 +96,7 @@ module FunWith
86
96
 
87
97
 
88
98
  def file_gsub( *args, &block )
89
- _must_be_a_file
99
+ must_be_file
90
100
 
91
101
  lines = []
92
102
  self.each_line do |line|
@@ -97,8 +107,8 @@ module FunWith
97
107
  end
98
108
 
99
109
  def file_gsub!( *args, &block )
100
- _must_be_a_file # raises error
101
- _must_be_writable # raises error
110
+ must_be_file # raises error
111
+ must_be_writable # raises error
102
112
 
103
113
  self.write( self.file_gsub( *args, &block ) )
104
114
  end
@@ -113,9 +123,9 @@ module FunWith
113
123
 
114
124
  # TODO: If it's truncated to a longer length than the original file,
115
125
  # pad with zeros? That's how the UNIX truncate command works.
116
- def truncate( len )
117
- _must_be_a_file # raises error
118
- _must_be_writable # raises error
126
+ def truncate( len = 0 )
127
+ must_be_file # raises error
128
+ must_be_writable # raises error
119
129
 
120
130
  old_size = self.size
121
131
  padding = len > old_size ? "\0" * (len - old_size) : ""