fun_with_files 0.0.15 → 0.0.18

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.
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
@@ -0,0 +1,62 @@
1
+ module FunWith
2
+ module Testing
3
+ class TestFwfAssertions < FunWith::Testing::AssertionsTestCase
4
+ context "testing assertions" do
5
+ setup do
6
+ extended_test_case # sets @case, which is used to access to assertions
7
+ @case_class.install_fun_with_files_assertions
8
+ end
9
+
10
+ context "testing :assert_fwf_filepath()" do
11
+ should "pass all tests" do
12
+ testing_method :assert_fwf_filepath do
13
+ nope __FILE__
14
+ yep __FILE__.fwf_filepath
15
+
16
+ nope nil
17
+ nope :five
18
+ nope 5
19
+ nope [5]
20
+ nope "five"
21
+ end
22
+ end
23
+ end
24
+
25
+ context "testing :assert_file()" do
26
+ should "pass all tests" do
27
+ testing_method :assert_file do
28
+ yep __FILE__.fwf_filepath
29
+
30
+ nope __FILE__
31
+ nope nil
32
+ nope :five
33
+ nope 5
34
+ nope [5]
35
+ nope "five"
36
+ end
37
+ end
38
+ end
39
+
40
+ context "testing :assert_directory()" do
41
+ should "pass all tests" do
42
+ testing_method :assert_directory do
43
+ nope __FILE__
44
+ nope __FILE__.fwf_filepath
45
+
46
+ yep __FILE__.fwf_filepath.dirname
47
+ yep __FILE__.fwf_filepath.up
48
+ yep FunWith::Files.root
49
+
50
+ nope nil
51
+ nope :five
52
+ nope 5
53
+ nope [5]
54
+ nope "five"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,111 @@
1
+ require 'helper'
2
+
3
+ class TestMovingFiles < FunWith::Files::TestCase
4
+ context "inside a tmpdir" do
5
+ setup do
6
+ @src_dir = FilePath.tmpdir
7
+ @dst_dir = FilePath.tmpdir
8
+
9
+ assert_directory @src_dir
10
+ assert_directory @dst_dir
11
+
12
+ assert_empty_directory @src_dir
13
+ assert_empty_directory @dst_dir
14
+ end
15
+
16
+ teardown do
17
+ @src_dir.rm
18
+ @dst_dir.rm
19
+ assert_not_directory @src_dir
20
+ assert_not_directory @dst_dir
21
+ end
22
+
23
+ context "with a source file" do
24
+ setup do
25
+ @src_file = @src_dir / "file.txt"
26
+ @src_file.write( "Hello world" )
27
+
28
+ assert_file_not_empty( @src_file )
29
+ end
30
+
31
+
32
+ should "successfully move a file into a directory" do
33
+ dest = @dst_dir / "file.txt"
34
+
35
+ assert_no_file dest
36
+
37
+ @src_file.move @dst_dir
38
+
39
+ assert_file dest
40
+ end
41
+
42
+
43
+
44
+ # Seems dangerous to not have a concrete idea of what should happen when a move
45
+ # remove / create request takes place. Ideas:
46
+ # be able to mark a destination as a directory, so that it knows the file move
47
+ # is saying to
48
+ #
49
+ # a directory should be created
50
+ # a directory must exist for the move to occur
51
+ # nothing exists at the destination, so the file is given the name of <thing_what_didnt_exist>
52
+ #
53
+ should "fail to move a file to a non-existent directory" do
54
+ flunk "this actually moves the file (the file getting the name of the 'missing' directory, and I'm not sure that's wrong)"
55
+ not_a_dir = @dst_dir / "humblebrag"
56
+
57
+ assert_raises( Errno::ENOENT ) do
58
+ @src_file.move( not_a_dir )
59
+ end
60
+ end
61
+
62
+ should "fail to move a file owing to lack of privileges" do
63
+ write_protected_dir = @dst_dir / "write_protected_dir"
64
+ write_protected_dir.touch_dir
65
+
66
+ temporarily_write_protect( write_protected_dir ) do
67
+ assert_raises( Errno::EACCES ) do
68
+ @src_file.move( write_protected_dir )
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+
75
+
76
+ should "fail to move a non-existent file" do
77
+ f = @src_dir.join( "file.txt" )
78
+
79
+ assert_no_file( f )
80
+
81
+ assert_raises( Errno::ENOENT ) do
82
+ f.move( @dst_dir )
83
+ end
84
+ end
85
+ end
86
+
87
+ #
88
+ # should "successfully move a directory" do
89
+ # flunk "write test"
90
+ # end
91
+ #
92
+ # should "fail to move a non-existent directory" do
93
+ # flunk "write test"
94
+ # end
95
+ #
96
+ # should "fail to move a directory to a non-existent directory" do
97
+ # flunk "write test"
98
+ # end
99
+ #
100
+ # should "fail to move a directory owing to lack of privileges" do
101
+ #
102
+ # flunk "write test"
103
+ # end
104
+ # end
105
+
106
+ def temporarily_write_protect( f, &block )
107
+ f.chmod( "a-w" )
108
+ yield
109
+ f.chmod( "a+w" )
110
+ end
111
+ end
@@ -0,0 +1,22 @@
1
+ require 'helper'
2
+
3
+ class TestPermissionMethods < FunWith::Files::TestCase
4
+ context "checking for availability of permission methods" do
5
+ setup do
6
+ @filepath = FunWith::Files::FilePath.new("/")
7
+ end
8
+
9
+ should "have permission methods" do
10
+ assert_respond_to @filepath, :readable?
11
+ assert_respond_to @filepath, :writable?
12
+ assert_respond_to @filepath, :executable?
13
+ assert_respond_to @filepath, :chown
14
+ assert_respond_to @filepath, :chmod
15
+ assert_respond_to @filepath, :owner
16
+ end
17
+
18
+ should "have a root owner" do
19
+ assert_equal "root", @filepath.owner
20
+ end
21
+ end
22
+ end
@@ -13,6 +13,15 @@ class TestRootPath < FunWith::Files::TestCase
13
13
  rootify_and_test( obj, path )
14
14
  end
15
15
 
16
+ context "FunWith::Files.root" do
17
+ should "be a directory" do
18
+ assert_directory( FunWith::Files.root )
19
+ assert_empty_directory( FunWith::Files.root( :test, :tmp ) )
20
+ assert_empty_directory( FunWith::Files.root / :test / :tmp )
21
+ assert_empty_directory( FunWith::Files.root / "test" / "tmp" )
22
+ end
23
+ end
24
+
16
25
  def rootify_and_test( obj, path )
17
26
  RootPath.rootify( obj, path )
18
27
  assert obj.respond_to?(:root)
@@ -0,0 +1,17 @@
1
+ require 'helper'
2
+
3
+ class TestStatMethods < FunWith::Files::TestCase
4
+
5
+ context "checking for availability of stat-enabling methods" do
6
+ setup do
7
+ @filepath = FunWith::Files::FilePath.new("/")
8
+ end
9
+
10
+ should "have stat methods" do
11
+ assert_respond_to @filepath, :stat
12
+ assert_respond_to @filepath, :inode
13
+ assert_respond_to @filepath, :birthtime # this (and many others) come from Pathname
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,74 @@
1
+ require 'helper'
2
+
3
+ class TestTimestamping < FunWith::Files::TestCase
4
+ context "testing timestamping" do
5
+ setup do
6
+ @tmp_dir = FunWith::Files.root( 'test', 'tmp' )
7
+ @logfile = @tmp_dir / "apache.log"
8
+ @stamp_time = Time.new( 2000, 10, 13, 23, 59, 59 )
9
+ end
10
+
11
+ teardown do
12
+ `rm -rf #{@tmp_dir.join('*')}`
13
+ end
14
+
15
+ should "sequence files with datestamps" do
16
+ dates = %w(2012-11-30 1900-12-01 2727-06-14)
17
+
18
+ for date in dates
19
+ d = Date.new( * date.split("-").map(&:to_i) )
20
+
21
+ f = @logfile.timestamp( format: :ymd, time: d )
22
+ f.write( date )
23
+
24
+ fy = @logfile.timestamp( format: :y, time: d )
25
+ fy.write date[0..3]
26
+ end
27
+
28
+ for str in dates + %w(2012 1900 2727)
29
+ file = @tmp_dir / "apache.#{str}.log"
30
+ assert_file file
31
+ assert_file_contents file, str
32
+ end
33
+ end
34
+
35
+ should "timestamp files using the timestamp() method" do
36
+ timestampable_file = @tmp_dir / "timestamped.dat"
37
+
38
+ timestamped_file1 = timestampable_file.timestamp
39
+ timestamped_file2 = timestampable_file.timestamp( format: :y )
40
+
41
+ assert timestamped_file1 =~ /timestamped.\d{17}.dat$/
42
+ assert timestamped_file2 =~ /timestamped.\d{4}.dat$/
43
+ end
44
+
45
+ should "raise an error when invalid format requested" do
46
+ f = @tmp_dir / "apache.log"
47
+
48
+ stamped = f.timestamp
49
+
50
+ assert_raises Errors::TimestampFormatUnrecognized do
51
+ f.timestamp( format: :zztop )
52
+ end
53
+
54
+ # Symbols only!
55
+ assert_raises Errors::TimestampFormatUnrecognized do
56
+ f.timestamp( format: "ymd" )
57
+ end
58
+ end
59
+
60
+ should "update the timestamp of a file that already has one" do
61
+ f = "apache.19931020235959000.tgz".fwf_filepath
62
+ timestamped_file = f.timestamp( time: @stamp_time )
63
+
64
+ assert_equal "apache.20001013235959000.tgz", timestamped_file.path
65
+ end
66
+
67
+ should "be able to give the timestamp method a custom format" do
68
+ fmt = Utils::TimestampFormat.new.recognizer( /^\d{2}_\d{2}_\d{2}$/ ).strftime( "%m_%d_%y" )
69
+
70
+ timestamped_file = @logfile.timestamp( format: fmt, time: @stamp_time )
71
+ assert_equal "apache.10_13_00.log", timestamped_file.basename.path
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,71 @@
1
+ require 'helper'
2
+
3
+ class TestUtilsByteSize < FunWith::Files::TestCase
4
+ context "testing the thing" do
5
+ setup do
6
+ @bytie = Object.new
7
+ @bytie.extend FunWith::Files::Utils::ByteSize
8
+ end
9
+
10
+ should "respond to :to_bytes" do
11
+ assert_respond_to @bytie, :to_bytes
12
+ end
13
+
14
+ should "accurately convert strings to bytes" do
15
+ assert_bytes 1_000, "1000b"
16
+ assert_bytes 1_000, "1 kb"
17
+ assert_bytes 1_000, "0.001 MB"
18
+ assert_bytes 1_000, "0.000001 GB"
19
+
20
+ assert_bytes 1_234, "1234"
21
+ assert_bytes 1_234, " 1.234 kb "
22
+ assert_bytes 9_001, "9.001k" # it's over 9000!
23
+ end
24
+
25
+ context "converting expressions between units" do
26
+ should "handle simple cases" do
27
+ assert_converts "1KB", "B", "1000B"
28
+ assert_converts "2MB", "KB", "2000KB"
29
+ assert_converts "3GB", "MB", "3000MB"
30
+ assert_converts "4TB", "GB", "4000GB"
31
+ assert_converts "5PB", "TB", "5000TB"
32
+ end
33
+
34
+ should "be case insensitive" do
35
+ assert_converts "1kb", "B", "1000B"
36
+ assert_converts "1000k", "mb", "1mb"
37
+ assert_converts "1000m", "GB", "1GB"
38
+ assert_converts "2000 PB", " EB", "2 EB"
39
+ end
40
+
41
+ should "sometimes put space between number and unit" do
42
+ assert_converts "2000 PB", " EB", "2 EB"
43
+ end
44
+
45
+ should "reflect the unit styling that the caller sends" do
46
+ assert_converts "1kb", "b", "1000b" # uses the unit capitalization that the caller sends
47
+ end
48
+
49
+ should "sometimes use decimal points" do
50
+ assert_converts "900kb", "MB", "0.9MB"
51
+ assert_converts "930kb", "mb", "0.9mb"
52
+
53
+ assert_converts "99500b", "kb", "99.5kb"
54
+ assert_converts "1200MB", "gb", "1.2gb"
55
+ end
56
+
57
+ should "sometimes not use decimal points" do
58
+ assert_converts "100372b", "k", "100k"
59
+ assert_converts "1b", "GB", "0GB"
60
+ end
61
+ end
62
+ end
63
+
64
+ def assert_bytes( n, expr )
65
+ assert_equal n, @bytie.to_bytes( expr ), "to_bytes( #{expr} ) should resolve to #{n}"
66
+ end
67
+
68
+ def assert_converts( old_expr, new_units, new_expr )
69
+ assert_equal new_expr, @bytie.convert( old_expr, new_units )
70
+ end
71
+ end
@@ -0,0 +1,30 @@
1
+ require 'helper'
2
+
3
+ class TestUtilsSuccession < FunWith::Files::TestCase
4
+ USucc = FunWith::Files::Utils::Succession
5
+
6
+ context "testing Succession.get_successor_name()" do
7
+ should "succeed" do
8
+ with_digit_count( 4 ) do
9
+ assert_succession "file.0001.txt", "file.0002.txt"
10
+ assert_succession "file.txt", "file.0000.txt"
11
+ assert_succession "", "0000"
12
+ end
13
+ end
14
+ end
15
+
16
+ def with_digit_count( i, &block )
17
+ @digit_count = i
18
+ yield
19
+ end
20
+
21
+ def assert_succession( input, expected )
22
+ if defined?( @digit_count )
23
+ actual = USucc.get_successor_name( input, @digit_count )
24
+ else
25
+ actual = USucc.get_successor_name( input )
26
+ end
27
+
28
+ assert_equal( expected, actual, "Utils::Succession.get_successor_name() failed:\n\tinput: #{input}(#{input.class})\n\texpected: #{expected}(#{expected.class})\n\tactual: #{actual}(#{actual.class})")
29
+ end
30
+ end
@@ -0,0 +1,196 @@
1
+ require 'helper'
2
+
3
+ class TestWatchers < FunWith::Files::TestCase
4
+ context "testing Watcher as it benevolently watches over the files placed under its care" do
5
+ setup do
6
+ tmpdir # assigns @tmpdir a freshly created temp directory
7
+ self.watch @tmpdir
8
+ end
9
+
10
+ teardown do
11
+ @tmpdir.rm
12
+ end
13
+
14
+ should "watch an empty directory as a subdirectory and a file are added" do
15
+ @tmpdir.touch_dir :lethe do |d|
16
+ file = d.join( "forgotten_file.txt" )
17
+ file.write( "someone help me remember this" )
18
+
19
+ get_changes do
20
+ assert_changes :created, d, file
21
+ end
22
+
23
+ file.append( "\nbecause I don't trust my brain to keep track of things" )
24
+
25
+ get_changes(1) do
26
+ assert_changes :modified, file
27
+ end
28
+
29
+ d.rm
30
+
31
+ get_changes do
32
+ assert_changes :deleted, d, file
33
+ end
34
+ end
35
+ end
36
+
37
+ should "watch an empty directory as a bunch of changes happen" do
38
+ @tmpdir.touch_dir( :battles ) do |d0|
39
+ @tmpdir.touch_dir( :bunker_hill ) do |d1|
40
+ file0 = d1.join( "troop_movements.csv" )
41
+ file0.write( "My dearest Sarah,\n\tI fear this may be the last time I write to you. Our forces are outnumbered." )
42
+
43
+ get_changes do
44
+ assert_changes :created, d0, d1, file0
45
+ end
46
+
47
+ file0.append "Supplies are scarce and the horses have lost their patience with us."
48
+
49
+ get_changes(1) do
50
+ assert_changes :modified, file0
51
+ end
52
+
53
+ d1.rm
54
+
55
+ get_changes(2) do
56
+ assert_changes :deleted, d1, file0
57
+ end
58
+ end
59
+
60
+ d0.rm
61
+
62
+ get_changes(1) do
63
+ assert_changes :deleted, d0
64
+ end
65
+ end
66
+ end
67
+
68
+ should "watch for a file that doesn't exist yet" do
69
+ @tmpdir.touch_dir( "web_app" ) do |d|
70
+ watch( d.join( "restart.txt" ) )
71
+
72
+ get_changes(0)
73
+
74
+ restart_file = d.touch( "restart.txt" )
75
+
76
+ get_changes(1) do
77
+ assert_changes :created, restart_file
78
+ end
79
+
80
+ restart_file.rm
81
+
82
+ get_changes(1) do
83
+ assert_changes :deleted, restart_file
84
+ end
85
+
86
+ get_changes(0)
87
+ end
88
+ end
89
+
90
+ should "build out a filesystem using DirectoryBuilder" do
91
+ @tmpdir.touch_dir( :ebook ) do |ebook|
92
+ watch ebook
93
+
94
+ DirectoryBuilder.create( ebook ) do |builder|
95
+ builder.dir( :html ) do
96
+ builder.file "title_page.xhtml", "Make sure we get some neato art to go here."
97
+ builder.file "chapter1.xhtml", "Besta times, worsta times, y'know?"
98
+ builder.file "chapter2.xhtml", "I see a magpie perched on the roof."
99
+ end
100
+
101
+ builder.dir( :css ) do
102
+ builder.file "main.css", "p{ background-color: painfully-pink}"
103
+ builder.file "title_page.css", "body{ width: 80% }"
104
+ end
105
+
106
+ builder.dir( :images ) do
107
+ builder.file "cover.png", "We shoulda hired a graphic designer"
108
+ end
109
+ end
110
+
111
+ html_dir = ebook / :html
112
+
113
+ images_dir = ebook / :images
114
+ cover_file = images_dir / "cover.png"
115
+
116
+ get_changes(9) do
117
+ assert_changes :created,
118
+ # ebook, # already existed when the watcher started
119
+ html_dir,
120
+ html_dir / "title_page.xhtml",
121
+ ebook / :css,
122
+ ebook / :css / "main.css",
123
+ images_dir,
124
+ cover_file
125
+ end
126
+
127
+ cover_file.append ", Trevor worked out okay last time, can we use him again?"
128
+
129
+ # debugger
130
+
131
+ get_changes(2) do
132
+ assert_changes :modified, cover_file, images_dir, html_dir
133
+ end
134
+
135
+ images_dir.rm
136
+
137
+ get_changes(2) do
138
+ assert_changes :deleted, images_dir, cover_file, html_dir
139
+ end
140
+ end
141
+ end
142
+
143
+ should "only notice changes that aren't excluded by filters" do
144
+
145
+ @tmpdir.touch_dir( :application_code ) do |code|
146
+ watch( code )
147
+
148
+ @watcher.filter( notice: /\.cpp$/, ignore: /main.cpp$/ )
149
+
150
+ f0 = code.touch( "main.cpp" )
151
+ f1 = code.touch( "counter.cpp" )
152
+
153
+ get_changes( count: 1 ) do
154
+ assert_changes :added, f1
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ def watch( *paths )
161
+ @watcher = Watcher.watch( *paths ).sleep_interval( 0.01 )
162
+ end
163
+
164
+ def get_changes( count: nil, expected: nil, &block )
165
+ @changes = @watcher.update
166
+ yield if block_given?
167
+ assert_change_count( count ) unless count.nil?
168
+ assert_change_set( expected ) unless expected.nil?
169
+ end
170
+
171
+ def assert_changes( status, *paths )
172
+ assert_kind_of Hash, @changes
173
+
174
+ oopsies = {}
175
+
176
+ for path in paths
177
+ path_to_report = path.relative_path_from(@tmpdir).to_s
178
+
179
+ if @changes.has_key?( path )
180
+ oopsies[path_to_report] = :change_not_reported
181
+ elsif status != @changes[path]
182
+ oopsies[path_to_report] = { :expected => status, :actual => @changes[path] }
183
+ end
184
+
185
+ unless oopsies.fwf_blank?
186
+ assert false, "Unexpected:" + oopsies.inspect
187
+ end
188
+ end
189
+ end
190
+
191
+ def assert_change_count( i )
192
+ assert defined?( @changes )
193
+ assert_kind_of Hash, @changes
194
+ assert_length i, @changes
195
+ end
196
+ end