history_file 0.1.0
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.
- data/.document +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +98 -0
- data/Guardfile +45 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +32 -0
- data/Rakefile +35 -0
- data/VERSION +1 -0
- data/history_file.gemspec +93 -0
- data/lib/history_file/file_delegator.rb +146 -0
- data/lib/history_file/history_file.rb +47 -0
- data/lib/history_file.rb +2 -0
- data/spec/history_file/history_file_spec.rb +86 -0
- data/spec/spec_helper.rb +56 -0
- metadata +290 -0
data/.document
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use ruby-1.9.3@history_file
|
data/Gemfile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "shoulda", ">= 0"
|
10
|
+
gem "rdoc", "~> 3.12"
|
11
|
+
gem "bundler"
|
12
|
+
gem "jeweler", "~> 1.8.4"
|
13
|
+
gem "simplecov", ">= 0"
|
14
|
+
end
|
15
|
+
group :development, :test do
|
16
|
+
gem 'webmock'
|
17
|
+
gem 'rspec'
|
18
|
+
gem 'terminal-notifier'
|
19
|
+
gem 'guard'
|
20
|
+
gem 'guard-rspec'
|
21
|
+
gem 'guard-bundler'
|
22
|
+
gem 'guard-yard'
|
23
|
+
gem 'guard-spork'
|
24
|
+
gem 'rb-fsevent'
|
25
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (3.2.9)
|
5
|
+
i18n (~> 0.6)
|
6
|
+
multi_json (~> 1.0)
|
7
|
+
addressable (2.3.2)
|
8
|
+
childprocess (0.3.6)
|
9
|
+
ffi (~> 1.0, >= 1.0.6)
|
10
|
+
coderay (1.0.8)
|
11
|
+
crack (0.3.1)
|
12
|
+
diff-lcs (1.1.3)
|
13
|
+
ffi (1.1.5)
|
14
|
+
git (1.2.5)
|
15
|
+
guard (1.5.4)
|
16
|
+
listen (>= 0.4.2)
|
17
|
+
lumberjack (>= 1.0.2)
|
18
|
+
pry (>= 0.9.10)
|
19
|
+
thor (>= 0.14.6)
|
20
|
+
guard-bundler (1.0.0)
|
21
|
+
bundler (~> 1.0)
|
22
|
+
guard (~> 1.1)
|
23
|
+
guard-rspec (2.1.1)
|
24
|
+
guard (>= 1.1)
|
25
|
+
rspec (~> 2.11)
|
26
|
+
guard-spork (1.2.3)
|
27
|
+
childprocess (>= 0.2.3)
|
28
|
+
guard (>= 1.1)
|
29
|
+
spork (>= 0.8.4)
|
30
|
+
sys-proctable
|
31
|
+
guard-yard (2.0.1)
|
32
|
+
guard (>= 1.1.0)
|
33
|
+
yard (>= 0.7.0)
|
34
|
+
i18n (0.6.1)
|
35
|
+
jeweler (1.8.4)
|
36
|
+
bundler (~> 1.0)
|
37
|
+
git (>= 1.2.5)
|
38
|
+
rake
|
39
|
+
rdoc
|
40
|
+
json (1.7.5)
|
41
|
+
listen (0.5.3)
|
42
|
+
lumberjack (1.0.2)
|
43
|
+
method_source (0.8.1)
|
44
|
+
multi_json (1.3.7)
|
45
|
+
pry (0.9.10)
|
46
|
+
coderay (~> 1.0.5)
|
47
|
+
method_source (~> 0.8)
|
48
|
+
slop (~> 3.3.1)
|
49
|
+
rake (10.0.1)
|
50
|
+
rb-fsevent (0.9.2)
|
51
|
+
rdoc (3.12)
|
52
|
+
json (~> 1.4)
|
53
|
+
rspec (2.12.0)
|
54
|
+
rspec-core (~> 2.12.0)
|
55
|
+
rspec-expectations (~> 2.12.0)
|
56
|
+
rspec-mocks (~> 2.12.0)
|
57
|
+
rspec-core (2.12.0)
|
58
|
+
rspec-expectations (2.12.0)
|
59
|
+
diff-lcs (~> 1.1.3)
|
60
|
+
rspec-mocks (2.12.0)
|
61
|
+
shoulda (3.3.2)
|
62
|
+
shoulda-context (~> 1.0.1)
|
63
|
+
shoulda-matchers (~> 1.4.1)
|
64
|
+
shoulda-context (1.0.1)
|
65
|
+
shoulda-matchers (1.4.1)
|
66
|
+
activesupport (>= 3.0.0)
|
67
|
+
simplecov (0.7.1)
|
68
|
+
multi_json (~> 1.0)
|
69
|
+
simplecov-html (~> 0.7.1)
|
70
|
+
simplecov-html (0.7.1)
|
71
|
+
slop (3.3.3)
|
72
|
+
spork (0.9.2)
|
73
|
+
sys-proctable (0.9.2)
|
74
|
+
terminal-notifier (1.4.2)
|
75
|
+
thor (0.16.0)
|
76
|
+
webmock (1.9.0)
|
77
|
+
addressable (>= 2.2.7)
|
78
|
+
crack (>= 0.1.7)
|
79
|
+
yard (0.8.3)
|
80
|
+
|
81
|
+
PLATFORMS
|
82
|
+
ruby
|
83
|
+
|
84
|
+
DEPENDENCIES
|
85
|
+
bundler
|
86
|
+
guard
|
87
|
+
guard-bundler
|
88
|
+
guard-rspec
|
89
|
+
guard-spork
|
90
|
+
guard-yard
|
91
|
+
jeweler (~> 1.8.4)
|
92
|
+
rb-fsevent
|
93
|
+
rdoc (~> 3.12)
|
94
|
+
rspec
|
95
|
+
shoulda
|
96
|
+
simplecov
|
97
|
+
terminal-notifier
|
98
|
+
webmock
|
data/Guardfile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
# Uncomment next line if Gemfile contain `gemspec' command
|
7
|
+
# watch(/^.+\.gemspec/)
|
8
|
+
end
|
9
|
+
|
10
|
+
guard 'rspec', :version => 2 do
|
11
|
+
watch(%r{^spec/.+_spec\.rb$})
|
12
|
+
watch('spec/spec_helper.rb') { "spec" }
|
13
|
+
watch(%r{^lib/history_file/(.+)\.rb$}) { "spec" }
|
14
|
+
|
15
|
+
# Rails example
|
16
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
17
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
18
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
19
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
20
|
+
watch('config/routes.rb') { "spec/routing" }
|
21
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
22
|
+
|
23
|
+
# Turnip features and steps
|
24
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
25
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
|
30
|
+
watch('config/application.rb')
|
31
|
+
watch('config/environment.rb')
|
32
|
+
watch('config/environments/test.rb')
|
33
|
+
watch(%r{^config/initializers/.+\.rb$})
|
34
|
+
watch('Gemfile')
|
35
|
+
watch('Gemfile.lock')
|
36
|
+
watch('spec/spec_helper.rb') { :rspec }
|
37
|
+
watch('test/test_helper.rb') { :test_unit }
|
38
|
+
watch(%r{features/support/}) { :cucumber }
|
39
|
+
end
|
40
|
+
|
41
|
+
guard 'yard', port: 8808, stdout: '/dev/null' do
|
42
|
+
watch(%r{app/.+\.rb})
|
43
|
+
watch(%r{lib/.+\.rb})
|
44
|
+
watch(%r{ext/.+\.c})
|
45
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Jannis Hermanns
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
HistoryFile
|
2
|
+
===========
|
3
|
+
|
4
|
+
Behaves like a {File} class and does some convenience stuff
|
5
|
+
around a {HistoryFile::FileDelegator} instance. It all
|
6
|
+
revolves about defining a time offset. If however, you want
|
7
|
+
to access different versions of a file, use it like this:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
> f = HistoryFile[1.day.ago].new("/tmp/foo.txt", "w")
|
11
|
+
=> #<File:/tmp/2012.11.02-foo.txt>
|
12
|
+
```
|
13
|
+
|
14
|
+
The returned {HistoryFile::FileDelegator} object supports all
|
15
|
+
methods that File has, but adds a date prefix to those methods
|
16
|
+
that revolve around a single file (reading, writing, etc.)
|
17
|
+
|
18
|
+
If a file for a given date is not available, {HistoryFile} falls
|
19
|
+
back to the freshest file that is older than the given date.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
> f = HistoryFile[3.days.ago].new("test.txt", "w")
|
23
|
+
=> #<File:./2012.11.12-test.txt>
|
24
|
+
> f.write("I am old")
|
25
|
+
=> 8
|
26
|
+
> f.close
|
27
|
+
=> nil
|
28
|
+
> HistoryFile[Date.today].read("test.txt")
|
29
|
+
=> "I am old"
|
30
|
+
> HistoryFile[10.days.ago].read("test.txt")
|
31
|
+
Errno::ENOENT: No such file or directory - ./2012.11.05-test.txt
|
32
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "history_file"
|
18
|
+
gem.homepage = "http://github.com/moviepilot/history_file"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A File like class with history support}
|
21
|
+
gem.description = %Q{A File like class that supports versioning by date and has a fallback to older files}
|
22
|
+
gem.email = "jannis@moviepilot.com"
|
23
|
+
gem.authors = ["Jannis Hermanns"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "history_file"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jannis Hermanns"]
|
12
|
+
s.date = "2012-11-16"
|
13
|
+
s.description = "A File like class that supports versioning by date and has a fallback to older files"
|
14
|
+
s.email = "jannis@moviepilot.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rvmrc",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"Guardfile",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"VERSION",
|
29
|
+
"history_file.gemspec",
|
30
|
+
"lib/history_file.rb",
|
31
|
+
"lib/history_file/file_delegator.rb",
|
32
|
+
"lib/history_file/history_file.rb",
|
33
|
+
"spec/history_file/history_file_spec.rb",
|
34
|
+
"spec/spec_helper.rb"
|
35
|
+
]
|
36
|
+
s.homepage = "http://github.com/moviepilot/history_file"
|
37
|
+
s.licenses = ["MIT"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = "1.8.24"
|
40
|
+
s.summary = "A File like class with history support"
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
s.specification_version = 3
|
44
|
+
|
45
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
46
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
47
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
48
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
49
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
50
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
51
|
+
s.add_development_dependency(%q<webmock>, [">= 0"])
|
52
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
53
|
+
s.add_development_dependency(%q<terminal-notifier>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<guard>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<guard-bundler>, [">= 0"])
|
57
|
+
s.add_development_dependency(%q<guard-yard>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<guard-spork>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<rb-fsevent>, [">= 0"])
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
62
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
63
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
64
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
65
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
66
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
67
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
68
|
+
s.add_dependency(%q<terminal-notifier>, [">= 0"])
|
69
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
70
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
71
|
+
s.add_dependency(%q<guard-bundler>, [">= 0"])
|
72
|
+
s.add_dependency(%q<guard-yard>, [">= 0"])
|
73
|
+
s.add_dependency(%q<guard-spork>, [">= 0"])
|
74
|
+
s.add_dependency(%q<rb-fsevent>, [">= 0"])
|
75
|
+
end
|
76
|
+
else
|
77
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
78
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
79
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
80
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
81
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
82
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
83
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
84
|
+
s.add_dependency(%q<terminal-notifier>, [">= 0"])
|
85
|
+
s.add_dependency(%q<guard>, [">= 0"])
|
86
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
87
|
+
s.add_dependency(%q<guard-bundler>, [">= 0"])
|
88
|
+
s.add_dependency(%q<guard-yard>, [">= 0"])
|
89
|
+
s.add_dependency(%q<guard-spork>, [">= 0"])
|
90
|
+
s.add_dependency(%q<rb-fsevent>, [">= 0"])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module HistoryFile
|
2
|
+
# This class delegates all method calls to the {File} class.
|
3
|
+
# The generic methods that don't revolve around one specific
|
4
|
+
# file, {File.join} for example, are just passed on. These
|
5
|
+
# methods are defined in
|
6
|
+
# {HistoryFile::FileDelegator::EXCLUDED_METHODS}.
|
7
|
+
#
|
8
|
+
# The methods that revolve around doing something with one
|
9
|
+
# specific file, however, will be called with an altered
|
10
|
+
# Filename. Consider this example:
|
11
|
+
#
|
12
|
+
# > fd = HistoryFile::FileDelegator.new("some_prefix")
|
13
|
+
# > fd.open("/path/to/my_file.txt", "w") do |io|
|
14
|
+
# > io.puts "hello there"
|
15
|
+
# > end
|
16
|
+
# > puts fd.read("/path/to/my_file.txt")
|
17
|
+
# => hello there
|
18
|
+
#
|
19
|
+
# This will pass on your block to {File.open}, but with a
|
20
|
+
# prefixed the filename. So what's really called is:
|
21
|
+
#
|
22
|
+
# File.open("/path/to/some_prefix-my_file.txt")
|
23
|
+
#
|
24
|
+
# For methods that get a bunch of filenames, but only filenames,
|
25
|
+
# as arguments, all of the filenames are patched to include the
|
26
|
+
# date prefix. These methods are defined in
|
27
|
+
# {HistoryFile::FileDelegator::BULK_METHODS}
|
28
|
+
#
|
29
|
+
# You shouldn't need to instanciate this class directly,
|
30
|
+
# {HistoryFile} wraps this for you.
|
31
|
+
class FileDelegator
|
32
|
+
EXCLUDED_METHODS = [
|
33
|
+
:absolute_path,
|
34
|
+
:basename,
|
35
|
+
:catname,
|
36
|
+
:chmod,
|
37
|
+
:chown,
|
38
|
+
:compare,
|
39
|
+
:copy,
|
40
|
+
:directory?,
|
41
|
+
:dirname,
|
42
|
+
:expand_path,
|
43
|
+
:extname,
|
44
|
+
:fnmatch,
|
45
|
+
:fnmatch?,
|
46
|
+
:identical?,
|
47
|
+
:install,
|
48
|
+
:join,
|
49
|
+
:lchown,
|
50
|
+
:link,
|
51
|
+
:makedirs,
|
52
|
+
:move,
|
53
|
+
:path,
|
54
|
+
:realdirpath,
|
55
|
+
:realpath,
|
56
|
+
:rename,
|
57
|
+
:split,
|
58
|
+
:umask,
|
59
|
+
:utime
|
60
|
+
]
|
61
|
+
|
62
|
+
BULK_METHODS = [
|
63
|
+
:delete,
|
64
|
+
:unlink,
|
65
|
+
:safe_unlink
|
66
|
+
]
|
67
|
+
|
68
|
+
# @param prefix [String] The prefix for all methods that revolve around
|
69
|
+
# filenames
|
70
|
+
# @param fallback_glob [String] If you want to fall back to an alphabetically
|
71
|
+
# smaller file on Errno::ENOENT, you can supply a fallback glob here. It
|
72
|
+
# will be used with {Dir.glob} to find all candidates (so this should match
|
73
|
+
# all prefixes)
|
74
|
+
def initialize(prefix, fallback_glob = nil)
|
75
|
+
@prefix = prefix
|
76
|
+
@fallback_glob = fallback_glob
|
77
|
+
end
|
78
|
+
|
79
|
+
# Either
|
80
|
+
# - passes on the call directly to File (why am I not
|
81
|
+
# removing this feature?) or
|
82
|
+
# - adds the date prefix to the first argument or
|
83
|
+
# - adds the date prefix to all arguments
|
84
|
+
def delegate(*args, &block)
|
85
|
+
method = args.slice!(0,1).first
|
86
|
+
if EXCLUDED_METHODS.include?(method)
|
87
|
+
File.send(method, *args, &block)
|
88
|
+
elsif BULK_METHODS.include?(method)
|
89
|
+
delegate_with_patched_filenames(method, *args, &block)
|
90
|
+
else
|
91
|
+
delegate_with_patched_filename(method, *args, &block)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def prefixed_filename(f)
|
96
|
+
dir = File.dirname(f.to_s)
|
97
|
+
file = File.basename(f.to_s)
|
98
|
+
File.join(dir, "#{@prefix}-#{file}")
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def file_fallback(original_filename, target_filename)
|
104
|
+
return false unless @fallback_glob
|
105
|
+
dir = File.dirname(original_filename.to_s)
|
106
|
+
file = File.basename(original_filename.to_s)
|
107
|
+
glob = File.join(dir, @fallback_glob+file)
|
108
|
+
candidates = Dir[glob].sort.select do |c|
|
109
|
+
c < target_filename
|
110
|
+
end.last
|
111
|
+
end
|
112
|
+
|
113
|
+
# Treats the first argument of the method as a file and passes
|
114
|
+
# the rest on as is. If we get a file not found exception, we'll
|
115
|
+
# fall back to the next older version we find.
|
116
|
+
def delegate_with_patched_filename(method, *args, &block)
|
117
|
+
filename = args.slice!(0,1).first
|
118
|
+
pf = prefixed_filename(filename)
|
119
|
+
begin
|
120
|
+
File.send(method, pf, *args, &block)
|
121
|
+
rescue Errno::ENOENT => e
|
122
|
+
raise e unless fallback = file_fallback(filename, pf)
|
123
|
+
File.send(method, fallback, *args, &block)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Treats all arguments of the methods as files and prepends the
|
128
|
+
# history prefix
|
129
|
+
def delegate_with_patched_filenames(method, *args, &block)
|
130
|
+
pfs = args.map{ |f| prefixed_filename(f) }
|
131
|
+
File.send(method, *pfs, &block)
|
132
|
+
end
|
133
|
+
|
134
|
+
# TL;DR: This is essentially a method missing, but only for the class
|
135
|
+
# methods of {File} and without the side effects.
|
136
|
+
#
|
137
|
+
# Since method missing causes weird things (especially in tests and
|
138
|
+
# puts), we just define all methods. Keeps me from overwriting/aliasing
|
139
|
+
# all the methods as I would have to do when inheriting from File.
|
140
|
+
((File.methods-Object.methods)+[:new]).sort.each do |m|
|
141
|
+
self.send(:define_method, m) do |*args, &block|
|
142
|
+
delegate(*([m]+args), &block)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Behaves like a {File} class and does some convenience stuff
|
2
|
+
# around a {HistoryFile::FileDelegator} instance. It all
|
3
|
+
# revolves about defining a time offset. If however, you want
|
4
|
+
# to access different versions of a file, use it like this:
|
5
|
+
#
|
6
|
+
# > f = HistoryFile[1.day.ago].new("/tmp/foo.txt", "w")
|
7
|
+
# => #<File:/tmp/2012.11.02-foo.txt>
|
8
|
+
#
|
9
|
+
# The returned {HistoryFile::FileDelegator} object supports all
|
10
|
+
# methods that File has, but adds a date prefix to those methods
|
11
|
+
# that revolve around a single file (reading, writing, etc.)
|
12
|
+
#
|
13
|
+
# If a file for a given date is not available, {HistoryFile} falls
|
14
|
+
# back to the freshest file that is older than the given date.
|
15
|
+
#
|
16
|
+
# > f = HistoryFile[3.days.ago].new("test.txt", "w")
|
17
|
+
# => #<File:./2012.11.12-test.txt>
|
18
|
+
# > f.write("I am old")
|
19
|
+
# => 8
|
20
|
+
# > f.close
|
21
|
+
# => nil
|
22
|
+
# > HistoryFile[Date.today].read("test.txt")
|
23
|
+
# => "I am old"
|
24
|
+
# > HistoryFile[10.days.ago].read("test.txt")
|
25
|
+
# Errno::ENOENT: No such file or directory - ./2012.11.05-test.txt
|
26
|
+
#
|
27
|
+
#
|
28
|
+
module HistoryFile
|
29
|
+
|
30
|
+
def self.[](offset)
|
31
|
+
validate_offset(offset)
|
32
|
+
fallback_glob = "[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]-"
|
33
|
+
FileDelegator.new(prefix(offset), fallback_glob)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def self.prefix(offset)
|
39
|
+
offset.strftime("%Y.%m.%d")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.validate_offset(offset)
|
43
|
+
return if offset.respond_to?(:strftime)
|
44
|
+
raise ArgumentError, "Offset #{offset} must respond to strftime"
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/history_file.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
describe HistoryFile::FileDelegator do
|
5
|
+
|
6
|
+
let(:fd){ HistoryFile::FileDelegator.new("some_prefix") }
|
7
|
+
|
8
|
+
after(:all) do
|
9
|
+
File.unlink("some_prefix-rspec_tmp_test.txt") rescue nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "delegates generic methods to File directly" do
|
13
|
+
File.should_receive(:split).with("/some/path")
|
14
|
+
fd.split("/some/path")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "delegates bulk methods with all parameters prefixed" do
|
18
|
+
offset = Date.parse("1983-04-03")
|
19
|
+
File.should_receive(:unlink).with("./1983.04.03-file1", "./1983.04.03-file2")
|
20
|
+
HistoryFile[offset].unlink("file1", "file2")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "instanciating new File instances works as expected" do
|
24
|
+
f = fd.new("rspec_tmp_test.txt", "w")
|
25
|
+
f.write("works")
|
26
|
+
f.close
|
27
|
+
File.read("some_prefix-rspec_tmp_test.txt").should == "works"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "writing into a file using a block works as expected" do
|
31
|
+
fd.open("rspec_tmp_test.txt", "w") do |file|
|
32
|
+
file.write("works")
|
33
|
+
end
|
34
|
+
File.read("some_prefix-rspec_tmp_test.txt").should == "works"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe HistoryFile do
|
39
|
+
|
40
|
+
context "writing and reading files" do
|
41
|
+
it "uses a correct prefix" do
|
42
|
+
offset = Date.parse("1979-12-22")
|
43
|
+
HistoryFile[offset].open("rspec_tmp_test.txt", "w") do |file|
|
44
|
+
file.write("old dude")
|
45
|
+
end
|
46
|
+
File.read("1979.12.22-rspec_tmp_test.txt").should == "old dude"
|
47
|
+
File.unlink("1979.12.22-rspec_tmp_test.txt")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "doesn't allow offsets that don't respond to strftime" do
|
51
|
+
expect{
|
52
|
+
HistoryFile[:today].open("rspec_tmp_test.txt", "w")
|
53
|
+
}.to raise_error(ArgumentError, "Offset today must respond to strftime")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "falling back to older files" do
|
58
|
+
before(:all) do
|
59
|
+
[1,2,3,6,7].each do |i|
|
60
|
+
date = DateTime.now - i
|
61
|
+
HistoryFile[date].open("ht.txt", "w") do |file|
|
62
|
+
file.write "Day #{i}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
after(:all) do
|
68
|
+
[1,2,3,6,7].each do |i|
|
69
|
+
date = DateTime.now - i
|
70
|
+
HistoryFile[date].unlink("ht.txt")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "to yesterday's file" do
|
75
|
+
date = DateTime.now - 4
|
76
|
+
HistoryFile[date].read("ht.txt").should == "Day 6"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "to an error if nothing older exists" do
|
80
|
+
expect{
|
81
|
+
date = DateTime.now - 8
|
82
|
+
HistoryFile[date].read("ht.txt")
|
83
|
+
}.to raise_error(Errno::ENOENT)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spork'
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start
|
5
|
+
require 'bundler'
|
6
|
+
Bundler.require
|
7
|
+
|
8
|
+
Spork.prefork do
|
9
|
+
# Loading more in this block will cause your tests to run faster. However,
|
10
|
+
# if you change any configuration or code from libraries loaded here, you'll
|
11
|
+
# need to restart spork for it take effect.
|
12
|
+
|
13
|
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
14
|
+
ENV["RAILS_ENV"] ||= 'test'
|
15
|
+
# require File.expand_path("../../config/environment", __FILE__)
|
16
|
+
#require 'capybara/poltergeist'
|
17
|
+
|
18
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
19
|
+
# in spec/support/ and its subdirectories.
|
20
|
+
Dir[File.expand_path("../../spec/support/**/*.rb", __FILE__)].each {|f| require f}
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
# == Mock Framework
|
24
|
+
#
|
25
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
26
|
+
#
|
27
|
+
# config.mock_with :mocha
|
28
|
+
# config.mock_with :flexmock
|
29
|
+
# config.mock_with :rr
|
30
|
+
config.mock_with :rspec
|
31
|
+
|
32
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
module TestResponseExtensions
|
37
|
+
def json_body
|
38
|
+
JSON.parse(self.body)
|
39
|
+
end
|
40
|
+
|
41
|
+
def unauthorized?
|
42
|
+
code.to_i == 401
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def expect_id_and_return(id, ret_val)
|
47
|
+
lambda { |id| id.to_s.should == id.to_s; ret_val }
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
require File.expand_path("../../lib/history_file/", __FILE__)
|
53
|
+
|
54
|
+
# Spork.each_run do
|
55
|
+
# FactoryGirl.reload
|
56
|
+
# end
|
metadata
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: history_file
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jannis Hermanns
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: shoulda
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rdoc
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.12'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.12'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: jeweler
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.8.4
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.8.4
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: simplecov
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: webmock
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: terminal-notifier
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: guard
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: guard-rspec
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: guard-bundler
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: guard-yard
|
192
|
+
requirement: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
- !ruby/object:Gem::Dependency
|
207
|
+
name: guard-spork
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
209
|
+
none: false
|
210
|
+
requirements:
|
211
|
+
- - ! '>='
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
type: :development
|
215
|
+
prerelease: false
|
216
|
+
version_requirements: !ruby/object:Gem::Requirement
|
217
|
+
none: false
|
218
|
+
requirements:
|
219
|
+
- - ! '>='
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: rb-fsevent
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
none: false
|
226
|
+
requirements:
|
227
|
+
- - ! '>='
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
none: false
|
234
|
+
requirements:
|
235
|
+
- - ! '>='
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: '0'
|
238
|
+
description: A File like class that supports versioning by date and has a fallback
|
239
|
+
to older files
|
240
|
+
email: jannis@moviepilot.com
|
241
|
+
executables: []
|
242
|
+
extensions: []
|
243
|
+
extra_rdoc_files:
|
244
|
+
- LICENSE.txt
|
245
|
+
- README.rdoc
|
246
|
+
files:
|
247
|
+
- .document
|
248
|
+
- .rvmrc
|
249
|
+
- Gemfile
|
250
|
+
- Gemfile.lock
|
251
|
+
- Guardfile
|
252
|
+
- LICENSE.txt
|
253
|
+
- README.rdoc
|
254
|
+
- Rakefile
|
255
|
+
- VERSION
|
256
|
+
- history_file.gemspec
|
257
|
+
- lib/history_file.rb
|
258
|
+
- lib/history_file/file_delegator.rb
|
259
|
+
- lib/history_file/history_file.rb
|
260
|
+
- spec/history_file/history_file_spec.rb
|
261
|
+
- spec/spec_helper.rb
|
262
|
+
homepage: http://github.com/moviepilot/history_file
|
263
|
+
licenses:
|
264
|
+
- MIT
|
265
|
+
post_install_message:
|
266
|
+
rdoc_options: []
|
267
|
+
require_paths:
|
268
|
+
- lib
|
269
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
270
|
+
none: false
|
271
|
+
requirements:
|
272
|
+
- - ! '>='
|
273
|
+
- !ruby/object:Gem::Version
|
274
|
+
version: '0'
|
275
|
+
segments:
|
276
|
+
- 0
|
277
|
+
hash: 3192868008292833198
|
278
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
279
|
+
none: false
|
280
|
+
requirements:
|
281
|
+
- - ! '>='
|
282
|
+
- !ruby/object:Gem::Version
|
283
|
+
version: '0'
|
284
|
+
requirements: []
|
285
|
+
rubyforge_project:
|
286
|
+
rubygems_version: 1.8.24
|
287
|
+
signing_key:
|
288
|
+
specification_version: 3
|
289
|
+
summary: A File like class with history support
|
290
|
+
test_files: []
|