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
@@ -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