simple_file_store 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/Gemfile +3 -0
- data/History.txt +23 -0
- data/README.asciidoc +86 -0
- data/Rakefile +69 -0
- data/lib/mini_blank_slate.rb +13 -0
- data/lib/simple_file_store.rb +7 -0
- data/lib/simple_file_store/caching_file_store.rb +30 -0
- data/lib/simple_file_store/scalable_file_store.rb +29 -0
- data/lib/simple_file_store/secure_file_store.rb +21 -0
- data/lib/simple_file_store/simple_file_store.rb +158 -0
- data/lib/simple_file_store/version.rb +5 -0
- data/simple_file_store.gemspec +32 -0
- data/test/base.rb +50 -0
- data/test/caching_file_store_test.rb +85 -0
- data/test/combined_file_store_test.rb +5 -0
- data/test/fstore/test_caching_stores/.keep +0 -0
- data/test/fstore/test_scalable_stores/.keep +0 -0
- data/test/fstore/test_secure_stores/.keep +0 -0
- data/test/fstore/test_stores/.keep +0 -0
- data/test/loader.rb +1 -0
- data/test/scalable_file_store_test.rb +77 -0
- data/test/secure_file_store_test.rb +81 -0
- data/test/simple_file_store_test.rb +57 -0
- metadata +205 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/History.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
== 0.0.4 / 2011-12-17
|
2
|
+
* Completely rehauled the gem infrastructure:
|
3
|
+
* Dropped Bones in favor of Bundler for gem management;
|
4
|
+
* Added Bundler for managing development/test dependencies;
|
5
|
+
* Completely rewritten the rake tasks, to make them simpler and more useful;
|
6
|
+
* Ensured the library is 1.9.2 compatible;
|
7
|
+
* Changed the base class from SimpleFileStore to SimpleFileStore::Base, which
|
8
|
+
allows us to use the module as a global namespace for project related code;
|
9
|
+
* Simplified tests;
|
10
|
+
* Various other cleanup.
|
11
|
+
|
12
|
+
== 0.0.3 / 2011-07-02
|
13
|
+
* Dropped using the BlankSlate gem in favor of a minimal, local lib;
|
14
|
+
* Introduced a features() class methods for easily adding store features.
|
15
|
+
|
16
|
+
== 0.0.2 / 2011-03-07
|
17
|
+
* Added CachingFileStore, cleaned up and simplified all existing Stores;
|
18
|
+
* Added automated/unit testing for everything.
|
19
|
+
|
20
|
+
== 0.0.1 / 2011-02-10
|
21
|
+
|
22
|
+
* 1 major enhancement:
|
23
|
+
* Birthday!
|
data/README.asciidoc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
Simple File Store
|
2
|
+
=================
|
3
|
+
Alexandru Ungur <alexaandru@gmail.com>
|
4
|
+
:icons:
|
5
|
+
:toc:
|
6
|
+
:website: http://github.com/alexaandru/simple_file_store
|
7
|
+
|
8
|
+
Description
|
9
|
+
-----------
|
10
|
+
|
11
|
+
A micro-framework for storing and loading files to/from the filesystem.
|
12
|
+
|
13
|
+
Features/Problems
|
14
|
+
-----------------
|
15
|
+
|
16
|
+
* Simplicity! which makes it trivial to extend (see SecureFileStore or ScalableFileStore)
|
17
|
+
* Handles the details of file_name composition/decomposition in a DSLish way
|
18
|
+
* Handles file content storing/loading to/from file
|
19
|
+
* TODO: implement validations (file name components should not be blank, etc.)
|
20
|
+
|
21
|
+
Synopsis
|
22
|
+
--------
|
23
|
+
|
24
|
+
.Example Usage
|
25
|
+
--------------------------------------------------------------------
|
26
|
+
class MyUpload < SimpleFileStore::Base
|
27
|
+
|
28
|
+
features :scalable, :encrypted
|
29
|
+
|
30
|
+
file_name_tokens :user_id, :category_id, :timestamp
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
params = {:content => "IO or string", :user_id => 1, :category_id => 17}
|
35
|
+
m = MyUpload.new(params) # file content is saved to fstore/my_uploads/29/b7/f2/25/1-17-1324284042.txt
|
36
|
+
m.file_name # => "1-17-1324284042.txt"
|
37
|
+
|
38
|
+
to load, simply call:
|
39
|
+
m = MyUpload.new(:file_name => "1-17-1324284042.txt")
|
40
|
+
m.content # => "file content"
|
41
|
+
--------------------------------------------------------------------
|
42
|
+
|
43
|
+
Requirements
|
44
|
+
------------
|
45
|
+
|
46
|
+
* ActiveSupport (for Inflections);
|
47
|
+
* Encryption Store needs an encryption library;
|
48
|
+
|
49
|
+
Install
|
50
|
+
-------
|
51
|
+
|
52
|
+
----------------------------------
|
53
|
+
gem install simple_file_store
|
54
|
+
|
55
|
+
or add this to Gemfile:
|
56
|
+
|
57
|
+
gem 'simple_file_store'
|
58
|
+
----------------------------------
|
59
|
+
|
60
|
+
License
|
61
|
+
-------
|
62
|
+
|
63
|
+
(The MIT License)
|
64
|
+
|
65
|
+
Copyright (c) 2011 Alexandru Ungur
|
66
|
+
|
67
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
68
|
+
a copy of this software and associated documentation files (the
|
69
|
+
'Software'), to deal in the Software without restriction, including
|
70
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
71
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
72
|
+
permit persons to whom the Software is furnished to do so, subject to
|
73
|
+
the following conditions:
|
74
|
+
|
75
|
+
The above copyright notice and this permission notice shall be
|
76
|
+
included in all copies or substantial portions of the Software.
|
77
|
+
|
78
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
79
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
80
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
81
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
82
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
83
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
84
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
85
|
+
|
86
|
+
// vim: set syntax=asciidoc:
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
def root
|
4
|
+
Pathname.new(File.dirname(__FILE__))
|
5
|
+
end
|
6
|
+
|
7
|
+
def test_files
|
8
|
+
Dir[root.join('test', '*_test.rb')].map{|x| x.inspect}.join(' ')
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :test do
|
12
|
+
desc "Check code quality"
|
13
|
+
task :quality do
|
14
|
+
%w|roodi flog flay|.each do |e|
|
15
|
+
puts "=== #{e.capitalize} #{'=' * 80}"
|
16
|
+
sh "#{e} lib/**/*.rb"
|
17
|
+
puts
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
namespace :unit do
|
22
|
+
test_files.split(' ').each do |tt|
|
23
|
+
name = File.basename(tt, '.rb"')[0..-6]
|
24
|
+
desc "Run #{name} unit test"
|
25
|
+
task name do
|
26
|
+
sh "ruby #{root.join('test', 'loader.rb')} #{tt}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "List the tests spec"
|
32
|
+
task :spec do
|
33
|
+
require 'test/unit'
|
34
|
+
test_files.delete('"').split(' ').sort.each do |file|
|
35
|
+
out = Test::Unit::TestCase.new(nil).capture_io { load file }.join
|
36
|
+
klass = File.basename(file, '.rb').classify
|
37
|
+
|
38
|
+
unless Object.const_defined?(klass.to_s)
|
39
|
+
puts "Skipping #{klass} because it doesn't map to a Class"
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
43
|
+
klass = klass.constantize
|
44
|
+
name = klass.name.gsub('Test', '')
|
45
|
+
puts name, '-' * name.size
|
46
|
+
test_methods = klass.instance_methods.grep(/^test/).map {|s| s.to_s.gsub(/^test: /, '')}.sort
|
47
|
+
test_methods.each { |m| puts " " << m }
|
48
|
+
puts out.nil? || out.empty? ? nil : [out, nil]
|
49
|
+
end
|
50
|
+
|
51
|
+
exit 1 # to skip running tests suite
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Run all tests"
|
56
|
+
task :test do
|
57
|
+
sh "ruby #{root.join('test', 'loader.rb')} #{test_files}"
|
58
|
+
end
|
59
|
+
|
60
|
+
namespace :test do
|
61
|
+
task :prepare_coverage do
|
62
|
+
ENV['COVERAGE'] = '1'
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "Run tests coverage report"
|
66
|
+
task :coverage => [:prepare_coverage, :test]
|
67
|
+
end
|
68
|
+
|
69
|
+
task :default => :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#
|
2
|
+
# Credit goes to Jim Weirich for the BlankSlate technique,
|
3
|
+
# adapted from his example at: http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
|
4
|
+
#
|
5
|
+
class MiniBlankSlate
|
6
|
+
|
7
|
+
BlankMethods = /^__|^instance_|object_id$|class$|inspect$|respond_to\?$/.freeze
|
8
|
+
|
9
|
+
instance_methods.each do |m|
|
10
|
+
m =~ BlankMethods or undef_method(m)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#
|
2
|
+
# This adds a transparent file caching layer.
|
3
|
+
#
|
4
|
+
# Behavior: when neither file_name nor content are
|
5
|
+
# provided, the generate_content() method is called
|
6
|
+
# and the output loaded into file (and thus, the
|
7
|
+
# file autosaved).
|
8
|
+
#
|
9
|
+
# Requirements: a generate_content() method.
|
10
|
+
#
|
11
|
+
# TODO: Implement an easy way to select the caching method
|
12
|
+
# vs. use the current convention.
|
13
|
+
# TODO: imlpement some hooks in base class, so that redefining
|
14
|
+
# methods should not be necessary.
|
15
|
+
#
|
16
|
+
|
17
|
+
module CachingFileStore
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def load_or_store!
|
22
|
+
self.content = generate_content unless File.exist?(path)
|
23
|
+
open {|f| content.nil? ? pull(f) : push(f)}
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_content
|
27
|
+
raise NoMethodError, "You must implement the method yourself"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#
|
2
|
+
# This adds transparent file path scalability (by using
|
3
|
+
# 4 depth trees hierarchy based on the file name).
|
4
|
+
#
|
5
|
+
# This allows one to have as many files (as the underlying
|
6
|
+
# filesystem allows) without every having to worry about
|
7
|
+
# too many files under one folder.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'digest'
|
11
|
+
module ScalableFileStore
|
12
|
+
|
13
|
+
def path
|
14
|
+
scale_path
|
15
|
+
root.join(self.class.name.tableize, @inter_tree, file_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def scale_path
|
21
|
+
raise ArgumentError, "Cowardly refusing to scale empty filename" if file_name.nil?
|
22
|
+
|
23
|
+
@inter_tree ||= begin
|
24
|
+
h = Digest::SHA256.hexdigest(file_name)
|
25
|
+
File.join(*[h[0,2],h[2,2],h[4,2],h[6,2]].reject{|z| z.nil? || z.empty?})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# This adds transparent file content encryption/decryption
|
3
|
+
# on top of SimpleFileStore
|
4
|
+
#
|
5
|
+
# TODO: Implement an easy way to select the encryptor/decryptor.
|
6
|
+
#
|
7
|
+
|
8
|
+
module SecureFileStore
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def pull(f)
|
13
|
+
raw = f.read
|
14
|
+
self.content = App.decrypt(raw) || raw
|
15
|
+
end
|
16
|
+
|
17
|
+
def push(f)
|
18
|
+
f.write(App.encrypt(content))
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
#
|
2
|
+
# SimpleFileStore implements a micro-framework for mundane file
|
3
|
+
# operations such as storing or loading a file.
|
4
|
+
#
|
5
|
+
# At it's core it does one of two things:
|
6
|
+
#
|
7
|
+
# * auto loads a file (if :file_name => <file_name> is passed)
|
8
|
+
# * auto stores a file (if :content => <content> is passed)
|
9
|
+
#
|
10
|
+
# To put it simple, if you give it some content, it will store it
|
11
|
+
# and give you a file name. If you give it a file name it will load
|
12
|
+
# the file content.
|
13
|
+
#
|
14
|
+
# This class is not intended to perform any other work, than just
|
15
|
+
# this basic one. Any extra functionality will be layered on top
|
16
|
+
# of it (see for example ScalableFileStore or SecureFileStore).
|
17
|
+
#
|
18
|
+
# It also offers a very handy way of composing/deomposing file names:
|
19
|
+
# you simply indicate the tokens that should compose the filename,
|
20
|
+
# and it will auto construct the file name (on content auto saving),
|
21
|
+
# respectiely it will decompose the file name into tokens when auto
|
22
|
+
# loading date from file.
|
23
|
+
#
|
24
|
+
|
25
|
+
#
|
26
|
+
# TODO: implement further validations (file name components should
|
27
|
+
# not be blank, etc.);
|
28
|
+
# TODO: make FileStoreRoot and Separator configurable.
|
29
|
+
#
|
30
|
+
require 'pathname'
|
31
|
+
require 'fileutils'
|
32
|
+
require 'rubygems'
|
33
|
+
require 'active_support/inflector'
|
34
|
+
|
35
|
+
module SimpleFileStore
|
36
|
+
|
37
|
+
class Base < MiniBlankSlate
|
38
|
+
|
39
|
+
KnownFeatures = Dir.glob(File.join(File.dirname(__FILE__), '*_file_store.rb')).map do |f|
|
40
|
+
f =~ %r|.*/([a-z]+)_file_store.rb$|
|
41
|
+
$1 && $1 != 'simple' ? $1 : nil
|
42
|
+
end.compact.freeze
|
43
|
+
FileStoreRoot = "fstore".freeze
|
44
|
+
Separator = "-".freeze
|
45
|
+
|
46
|
+
attr_accessor :root, :content_type, :timestamp, :usec
|
47
|
+
attr :content
|
48
|
+
attr :file_name
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def file_name_tokens(*args)
|
52
|
+
@file_name_tokens ||= []
|
53
|
+
unless args.empty?
|
54
|
+
@file_name_tokens.concat(args)
|
55
|
+
args.each do |token|
|
56
|
+
next if [:timestamp, :usec].include?(token.to_sym)
|
57
|
+
instance_eval { attr_accessor token }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@file_name_tokens
|
61
|
+
end
|
62
|
+
|
63
|
+
def features(*args)
|
64
|
+
args = args.flatten.compact
|
65
|
+
raise ArgumentError, "Unknown feature" unless args.all?{|a| KnownFeatures.include?(a.to_s)}
|
66
|
+
|
67
|
+
args.each do |a|
|
68
|
+
instance_eval { include "#{a}_file_store".classify.constantize }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(args = {})
|
74
|
+
init_root!
|
75
|
+
validate!
|
76
|
+
load_arguments(args)
|
77
|
+
load_or_store!
|
78
|
+
end
|
79
|
+
|
80
|
+
def path
|
81
|
+
root.join(self.class.name.tableize, file_name)
|
82
|
+
end
|
83
|
+
|
84
|
+
def file_name=(new_name)
|
85
|
+
return false if new_name.nil? || new_name.empty?
|
86
|
+
|
87
|
+
if new_name =~ /^(.*)\.(.*?)$/
|
88
|
+
@file_name, self.content_type = new_name, $2
|
89
|
+
file_name_tokens.zip($1.split(Separator)) {|(k, v)| __send__("#{k}=", v)}
|
90
|
+
else
|
91
|
+
raise ArgumentError, "Unrecognized file name: #{new_name}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def content=(new_content)
|
96
|
+
return false if new_content.nil?
|
97
|
+
|
98
|
+
@content = if new_content.respond_to?(:read) then new_content.read
|
99
|
+
elsif new_content.respond_to?(:to_s) then new_content.to_s
|
100
|
+
else new_content
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def open(flag = 'r+', &block)
|
105
|
+
path.dirname.mkpath
|
106
|
+
FileUtils.touch(path)
|
107
|
+
File.open(path, flag, &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
protected
|
111
|
+
|
112
|
+
def load_or_store!
|
113
|
+
open {|f| content.nil? ? pull(f) : push(f)}
|
114
|
+
end
|
115
|
+
|
116
|
+
def pull(f)
|
117
|
+
self.content = f.read
|
118
|
+
end
|
119
|
+
|
120
|
+
def push(f)
|
121
|
+
f.write(content)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def init_root!
|
127
|
+
self.root ||= case
|
128
|
+
when defined?(Rails) then Rails.root
|
129
|
+
when defined?(AppRoot) then AppRoot.is_a?(Pathname) ? AppRoot : Pathname.new(AppRoot.to_s)
|
130
|
+
else Pathname.new(FileUtils.pwd)
|
131
|
+
end.join(FileStoreRoot)
|
132
|
+
end
|
133
|
+
|
134
|
+
def validate!
|
135
|
+
raise Errno::ENOENT, "Root not found: #{root}!" unless root.exist?
|
136
|
+
end
|
137
|
+
|
138
|
+
def load_arguments(args)
|
139
|
+
# passed (as-is) args
|
140
|
+
args = {:content_type => 'csv'}.merge(args)
|
141
|
+
args.each {|(k,v)| __send__ "#{k}=", v}
|
142
|
+
|
143
|
+
# custom (builtin) args
|
144
|
+
t = Time.now
|
145
|
+
self.timestamp = t.to_i
|
146
|
+
self.usec = t.usec
|
147
|
+
|
148
|
+
# hybrid args (depending on passed+builtin)
|
149
|
+
@file_name ||= file_name_tokens.map{|k| __send__(k)}.join(Separator) << '.' << content_type
|
150
|
+
end
|
151
|
+
|
152
|
+
def file_name_tokens
|
153
|
+
self.class.file_name_tokens
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'simple_file_store/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'simple_file_store'
|
7
|
+
s.version = SimpleFileStore::VERSION
|
8
|
+
s.authors = ['Alex Ungur']
|
9
|
+
s.date = '2011-12-17'
|
10
|
+
s.email = ['alexaandru@gmail.com']
|
11
|
+
s.homepage = %q|http://rubygems.org/gems/simple_file_store|
|
12
|
+
s.summary = %q|A file storing/loading micro-framework.|
|
13
|
+
s.description = %q|A micro-framework for automating storing and loading files to/from the filesystem.|
|
14
|
+
|
15
|
+
s.rubyforge_project = 'simple_file_store'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
s.rdoc_options = ['--main', 'README.asciidoc']
|
22
|
+
|
23
|
+
# specify any dependencies here; for example:
|
24
|
+
s.add_runtime_dependency 'activesupport', ['~>2.3.0']
|
25
|
+
s.add_development_dependency 'rake'
|
26
|
+
s.add_development_dependency 'bundler'
|
27
|
+
s.add_development_dependency 'shoulda'
|
28
|
+
s.add_development_dependency 'simplecov'
|
29
|
+
s.add_development_dependency 'roodi'
|
30
|
+
s.add_development_dependency 'flay'
|
31
|
+
s.add_development_dependency 'flog'
|
32
|
+
end
|
data/test/base.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'shoulda'
|
6
|
+
if RUBY_VERSION >= '1.9.0'
|
7
|
+
require 'simplecov'
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter "/test/"
|
10
|
+
add_group "Libraries", "/lib/"
|
11
|
+
command_name "Combined Tests"
|
12
|
+
end if ENV['COVERAGE']
|
13
|
+
end
|
14
|
+
|
15
|
+
require './lib/simple_file_store'
|
16
|
+
# The testing fstore root
|
17
|
+
AppRoot = File.dirname(__FILE__)
|
18
|
+
|
19
|
+
module SimpleFileStore
|
20
|
+
|
21
|
+
class TestCase < Test::Unit::TestCase
|
22
|
+
|
23
|
+
def assert_dir_content_difference(dir, depth = ['*'], difference = 1)
|
24
|
+
raise ArgumentError, "Directory not found" unless File.exist?(dir)
|
25
|
+
before_content = Dir[File.join(dir, *depth)]
|
26
|
+
yield
|
27
|
+
after_content = Dir[File.join(dir, *depth)]
|
28
|
+
assert_equal before_content.size + difference, after_content.size,
|
29
|
+
"Expected an increase of #{difference} in '#{dir}' folder contents, got a difference of #{after_content.size - before_content.size}"
|
30
|
+
after_content - before_content
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert_no_dir_content_difference(dir, depth = ['*'], &block)
|
34
|
+
assert_dir_content_difference(dir, depth, 0, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def assert_attributes_set(ts, file_content, record_id, expected_file_name)
|
38
|
+
assert_equal file_content, ts.content, "Should set file/data"
|
39
|
+
assert_equal File.basename(expected_file_name), ts.file_name, "Should set file_name"
|
40
|
+
assert_equal record_id, ts.record_id, "Should set record ID"
|
41
|
+
assert ts.timestamp, "Should set timestamp"
|
42
|
+
assert ts.usec, "Should set timestamp/.usec"
|
43
|
+
assert_equal 'csv', ts.content_type, "Should set content_type"
|
44
|
+
assert_match %r|/test/fstore$|, ts.root.to_s, "Should set root"
|
45
|
+
assert_match %r|#{expected_file_name}$|, ts.path.to_s, "Should set path"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require './test/base'
|
2
|
+
|
3
|
+
class TestCachingStore < SimpleFileStore::Base
|
4
|
+
features :caching
|
5
|
+
|
6
|
+
file_name_tokens :record_id, :alt_rec_id
|
7
|
+
|
8
|
+
def generate_content
|
9
|
+
"auto generated content %s/%s" % [record_id, alt_rec_id]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestBadCachingStore < SimpleFileStore::Base
|
14
|
+
include CachingFileStore
|
15
|
+
|
16
|
+
file_name_tokens :res_id, :alt_rec_id
|
17
|
+
end
|
18
|
+
|
19
|
+
class CachingFileStoreTest < SimpleFileStore::TestCase
|
20
|
+
context "Auto-saving to CachingFileStore" do
|
21
|
+
setup do
|
22
|
+
@record_id = "102"
|
23
|
+
@alt_rec_id = "foobar"
|
24
|
+
@file_content = "auto generated content 102/foobar"
|
25
|
+
@file_names = assert_dir_content_difference('test/fstore/test_caching_stores') {
|
26
|
+
@ts = TestCachingStore.new(:record_id => @record_id, :alt_rec_id => @alt_rec_id)
|
27
|
+
}
|
28
|
+
@expected_file_name = "test/fstore/test_caching_stores/#{@record_id}-#{@alt_rec_id}.csv"
|
29
|
+
end
|
30
|
+
|
31
|
+
teardown do
|
32
|
+
@file_names.each {|f| File.unlink(f)} unless @file_names.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
should "save content to store" do
|
36
|
+
assert File.exist?(@expected_file_name)
|
37
|
+
assert_equal @expected_file_name, @file_names.first
|
38
|
+
end
|
39
|
+
|
40
|
+
should "set attributes" do
|
41
|
+
assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
should "raise an error if generate_content is not defined" do
|
45
|
+
assert_raise NoMethodError do
|
46
|
+
TestBadCachingStore.new(:record_id => @record_id, :alt_rec_id => @alt_rec_id)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "Auto-loading from CachingFileStore" do
|
52
|
+
setup do
|
53
|
+
@record_id = "1"
|
54
|
+
@alt_rec_id = "2"
|
55
|
+
@file_content = "xyz"
|
56
|
+
@file_name = "#{@record_id}#{TestCachingStore::Separator}#{@alt_rec_id}.csv"
|
57
|
+
@file_path = File.join('test', 'fstore', 'test_caching_stores', @file_name)
|
58
|
+
FileUtils.mkdir_p(File.dirname(@file_path))
|
59
|
+
File.open(@file_path, 'w') {|f| f.write @file_content}
|
60
|
+
@file_names = assert_no_dir_content_difference('test/fstore/test_caching_stores') {
|
61
|
+
@ts = TestCachingStore.new(:file_name => @file_name)
|
62
|
+
}
|
63
|
+
@expected_file_name = "test/fstore/test_caching_stores/#{@record_id}#{TestCachingStore::Separator}#{@alt_rec_id}.csv"
|
64
|
+
end
|
65
|
+
|
66
|
+
teardown do
|
67
|
+
File.unlink(@file_path)
|
68
|
+
end
|
69
|
+
|
70
|
+
should "load content from store" do
|
71
|
+
assert_equal @file_content, @ts.content
|
72
|
+
end
|
73
|
+
|
74
|
+
should "set attributes" do
|
75
|
+
assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def assert_attributes_set(ts, file_content, record_id, alt_rec_id, expected_file_name)
|
82
|
+
super(ts, file_content, record_id, expected_file_name)
|
83
|
+
assert_equal alt_rec_id, ts.alt_rec_id, "Should set record ID"
|
84
|
+
end
|
85
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/test/loader.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ARGV.each {|f| load f unless f =~ /^-/}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require './test/base'
|
2
|
+
|
3
|
+
class TestScalableStore < SimpleFileStore::Base
|
4
|
+
features :scalable
|
5
|
+
|
6
|
+
file_name_tokens :record_id, :alt_rec_id
|
7
|
+
end
|
8
|
+
|
9
|
+
class ScalableFileStoreTest < SimpleFileStore::TestCase
|
10
|
+
context "Auto-saving to ScalableFileStore" do
|
11
|
+
setup do
|
12
|
+
@record_id = "101"
|
13
|
+
@alt_rec_id = "2345678"
|
14
|
+
@file_content = "xyz"
|
15
|
+
@file_names = assert_dir_content_difference('test/fstore/test_scalable_stores', %w|* * * * **|) {
|
16
|
+
@ts = TestScalableStore.new(:content => @file_content, :record_id => @record_id, :alt_rec_id => @alt_rec_id)
|
17
|
+
}
|
18
|
+
@expected_file_name = "test/fstore/test_scalable_stores/bb/37/22/88/#{@record_id}-#{@alt_rec_id}.csv"
|
19
|
+
end
|
20
|
+
|
21
|
+
teardown do
|
22
|
+
@file_names.each do |f|
|
23
|
+
path = File.dirname(File.dirname(File.dirname(File.dirname(f))))
|
24
|
+
raise ArgumentError, "You may be 'rm -rf'-ing the wrong path: '#{path}'. Aborting!" unless \
|
25
|
+
path =~ %r|test/fstore/test_scalable_stores/.*|
|
26
|
+
FileUtils.rm_rf(path)
|
27
|
+
end if @file_names
|
28
|
+
end
|
29
|
+
|
30
|
+
should "save content to store" do
|
31
|
+
assert File.exist?(@expected_file_name)
|
32
|
+
assert_equal @expected_file_name, @file_names.first
|
33
|
+
end
|
34
|
+
|
35
|
+
should "set attributes" do
|
36
|
+
assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "Auto-loading from ScalableFileStore" do
|
41
|
+
setup do
|
42
|
+
@record_id = "1"
|
43
|
+
@alt_rec_id = "2"
|
44
|
+
@file_content = "xyz"
|
45
|
+
@file_name = "#{@record_id}#{TestScalableStore::Separator}#{@alt_rec_id}.csv"
|
46
|
+
@file_path = File.join('test', 'fstore', 'test_scalable_stores', '96', 'c2', 'c5', 'c0', @file_name)
|
47
|
+
FileUtils.mkdir_p(File.dirname(@file_path))
|
48
|
+
File.open(@file_path, 'w') {|f| f.write @file_content}
|
49
|
+
@file_names = assert_no_dir_content_difference('test/fstore/test_scalable_stores', %w|* * * * **|) {
|
50
|
+
@ts = TestScalableStore.new(:file_name => @file_name)
|
51
|
+
}
|
52
|
+
@expected_file_name = "test/fstore/test_scalable_stores/96/c2/c5/c0/#{@record_id}#{TestScalableStore::Separator}#{@alt_rec_id}.csv"
|
53
|
+
end
|
54
|
+
|
55
|
+
teardown do
|
56
|
+
path = File.dirname(File.dirname(File.dirname(File.dirname(@file_path))))
|
57
|
+
raise ArgumentError, "You may be 'rm -rf'-ing the wrong path: '#{path}'. Aborting!" unless \
|
58
|
+
path =~ %r|test/fstore/test_scalable_stores/.*|
|
59
|
+
FileUtils.rm_rf(path)
|
60
|
+
end
|
61
|
+
|
62
|
+
should "load content from store" do
|
63
|
+
assert_equal @file_content, @ts.content
|
64
|
+
end
|
65
|
+
|
66
|
+
should "set attributes" do
|
67
|
+
assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def assert_attributes_set(ts, file_content, record_id, alt_rec_id, expected_file_name)
|
74
|
+
super(ts, file_content, record_id, expected_file_name)
|
75
|
+
assert_equal alt_rec_id, ts.alt_rec_id, "Should set record ID"
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require './test/base'
|
2
|
+
|
3
|
+
# This is pretending to encrypt something.
|
4
|
+
module App
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def rot13(str)
|
8
|
+
str.tr "A-Za-z", "N-ZA-Mn-za-m"
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :encrypt :rot13
|
12
|
+
alias :decrypt :rot13
|
13
|
+
end
|
14
|
+
|
15
|
+
class TestSecureStore < SimpleFileStore::Base
|
16
|
+
features :secure
|
17
|
+
|
18
|
+
file_name_tokens :record_id, :alt_rec_id
|
19
|
+
end
|
20
|
+
|
21
|
+
class SecureFileStoreTest < SimpleFileStore::TestCase
|
22
|
+
context "Auto-saving to SecureFileStore" do
|
23
|
+
setup do
|
24
|
+
@record_id = "102"
|
25
|
+
@alt_rec_id = "foobar"
|
26
|
+
@file_content = "foo bar baz"
|
27
|
+
@file_names = assert_dir_content_difference('test/fstore/test_secure_stores') {
|
28
|
+
@ts = TestSecureStore.new(:content => @file_content, :record_id => @record_id, :alt_rec_id => @alt_rec_id)
|
29
|
+
}
|
30
|
+
@expected_file_name = "test/fstore/test_secure_stores/#{@record_id}-#{@alt_rec_id}.csv"
|
31
|
+
end
|
32
|
+
|
33
|
+
teardown do
|
34
|
+
@file_names.each {|f| File.unlink(f)} unless @file_names.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
should "save content to store" do
|
38
|
+
assert File.exist?(@expected_file_name)
|
39
|
+
assert_equal @expected_file_name, @file_names.first
|
40
|
+
end
|
41
|
+
|
42
|
+
should "set attributes" do
|
43
|
+
assert_attributes_set(@ts, @file_content, @record_id, @alt_rec_id, @expected_file_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "Auto-loading from SecureFileStore" do
|
48
|
+
setup do
|
49
|
+
@record_id = "1"
|
50
|
+
@alt_rec_id = "2"
|
51
|
+
@file_content = "xyz"
|
52
|
+
@file_name = "#{@record_id}#{TestSecureStore::Separator}#{@alt_rec_id}.csv"
|
53
|
+
@file_path = File.join('test', 'fstore', 'test_secure_stores', @file_name)
|
54
|
+
FileUtils.mkdir_p(File.dirname(@file_path))
|
55
|
+
File.open(@file_path, 'w') {|f| f.write @file_content}
|
56
|
+
@file_names = assert_no_dir_content_difference('test/fstore/test_secure_stores') {
|
57
|
+
@ts = TestSecureStore.new(:file_name => @file_name)
|
58
|
+
}
|
59
|
+
@expected_file_name = "test/fstore/test_secure_stores/#{@record_id}#{TestSecureStore::Separator}#{@alt_rec_id}.csv"
|
60
|
+
end
|
61
|
+
|
62
|
+
teardown do
|
63
|
+
File.unlink(@file_path)
|
64
|
+
end
|
65
|
+
|
66
|
+
should "load content from store" do
|
67
|
+
assert_equal App.decrypt(@file_content), @ts.content
|
68
|
+
end
|
69
|
+
|
70
|
+
should "set attributes" do
|
71
|
+
assert_attributes_set(@ts, App.decrypt(@file_content), @record_id, @alt_rec_id, @expected_file_name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def assert_attributes_set(ts, file_content, record_id, alt_rec_id, expected_file_name)
|
78
|
+
super(ts, file_content, record_id, expected_file_name)
|
79
|
+
assert_equal alt_rec_id, ts.alt_rec_id, "Should set record ID"
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require './test/base'
|
2
|
+
|
3
|
+
class TestStore < SimpleFileStore::Base
|
4
|
+
file_name_tokens :record_id, :timestamp, :usec
|
5
|
+
end
|
6
|
+
|
7
|
+
class SimpleFileStoreTest < SimpleFileStore::TestCase
|
8
|
+
context "Auto-saving to SimpleFileStore" do
|
9
|
+
setup do
|
10
|
+
@record_id = "100"
|
11
|
+
@file_content = "abc"
|
12
|
+
@file_names = assert_dir_content_difference('test/fstore/test_stores') {
|
13
|
+
@ts = TestStore.new(:content => @file_content, :record_id => @record_id)
|
14
|
+
@expected_file_name = File.join('test', TestStore::FileStoreRoot, 'test_stores',
|
15
|
+
"#{@ts.record_id}#{TestStore::Separator}#{@ts.timestamp}-#{@ts.usec}.#{@ts.content_type}")
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
teardown do
|
20
|
+
@file_names.each {|f| File.unlink(f)} unless @file_names.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
should "save content to store" do
|
24
|
+
assert File.exist?(@expected_file_name), "File '#{@expected_file_name}' should have been created!"
|
25
|
+
assert_equal @expected_file_name, @file_names.first
|
26
|
+
end
|
27
|
+
|
28
|
+
should "set attributes" do
|
29
|
+
assert_attributes_set(@ts, @file_content, @record_id, @expected_file_name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "Auto-loading from SimpleFileStore" do
|
34
|
+
setup do
|
35
|
+
@record_id = "101"
|
36
|
+
@file_content = "cde"
|
37
|
+
@file_name = "101-#{Time.now.to_i}-1.csv"
|
38
|
+
@file_path = File.join('test', 'fstore', 'test_stores', @file_name)
|
39
|
+
File.open(@file_path, 'w') {|f| f.write @file_content}
|
40
|
+
@file_names = assert_no_dir_content_difference('test/fstore/test_stores') {
|
41
|
+
@ts = TestStore.new(:file_name => @file_name)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
teardown do
|
46
|
+
FileUtils.rm_f(@file_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
should "load content from store" do
|
50
|
+
assert_equal @file_content, @ts.content
|
51
|
+
end
|
52
|
+
|
53
|
+
should "set attributes" do
|
54
|
+
assert_attributes_set(@ts, @file_content, @record_id, @file_path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_file_store
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Alex Ungur
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-12-17 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activesupport
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 3
|
33
|
+
- 0
|
34
|
+
version: 2.3.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rake
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: bundler
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: shoulda
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :development
|
78
|
+
version_requirements: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: simplecov
|
81
|
+
prerelease: false
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id005
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: roodi
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
type: :development
|
106
|
+
version_requirements: *id006
|
107
|
+
- !ruby/object:Gem::Dependency
|
108
|
+
name: flay
|
109
|
+
prerelease: false
|
110
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 3
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
version: "0"
|
119
|
+
type: :development
|
120
|
+
version_requirements: *id007
|
121
|
+
- !ruby/object:Gem::Dependency
|
122
|
+
name: flog
|
123
|
+
prerelease: false
|
124
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
type: :development
|
134
|
+
version_requirements: *id008
|
135
|
+
description: A micro-framework for automating storing and loading files to/from the filesystem.
|
136
|
+
email:
|
137
|
+
- alexaandru@gmail.com
|
138
|
+
executables: []
|
139
|
+
|
140
|
+
extensions: []
|
141
|
+
|
142
|
+
extra_rdoc_files: []
|
143
|
+
|
144
|
+
files:
|
145
|
+
- .gitignore
|
146
|
+
- Gemfile
|
147
|
+
- History.txt
|
148
|
+
- README.asciidoc
|
149
|
+
- Rakefile
|
150
|
+
- lib/mini_blank_slate.rb
|
151
|
+
- lib/simple_file_store.rb
|
152
|
+
- lib/simple_file_store/caching_file_store.rb
|
153
|
+
- lib/simple_file_store/scalable_file_store.rb
|
154
|
+
- lib/simple_file_store/secure_file_store.rb
|
155
|
+
- lib/simple_file_store/simple_file_store.rb
|
156
|
+
- lib/simple_file_store/version.rb
|
157
|
+
- simple_file_store.gemspec
|
158
|
+
- test/base.rb
|
159
|
+
- test/caching_file_store_test.rb
|
160
|
+
- test/combined_file_store_test.rb
|
161
|
+
- test/fstore/test_caching_stores/.keep
|
162
|
+
- test/fstore/test_scalable_stores/.keep
|
163
|
+
- test/fstore/test_secure_stores/.keep
|
164
|
+
- test/fstore/test_stores/.keep
|
165
|
+
- test/loader.rb
|
166
|
+
- test/scalable_file_store_test.rb
|
167
|
+
- test/secure_file_store_test.rb
|
168
|
+
- test/simple_file_store_test.rb
|
169
|
+
has_rdoc: true
|
170
|
+
homepage: http://rubygems.org/gems/simple_file_store
|
171
|
+
licenses: []
|
172
|
+
|
173
|
+
post_install_message:
|
174
|
+
rdoc_options:
|
175
|
+
- --main
|
176
|
+
- README.asciidoc
|
177
|
+
require_paths:
|
178
|
+
- lib
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
+
none: false
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
hash: 3
|
185
|
+
segments:
|
186
|
+
- 0
|
187
|
+
version: "0"
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
189
|
+
none: false
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
hash: 3
|
194
|
+
segments:
|
195
|
+
- 0
|
196
|
+
version: "0"
|
197
|
+
requirements: []
|
198
|
+
|
199
|
+
rubyforge_project: simple_file_store
|
200
|
+
rubygems_version: 1.5.2
|
201
|
+
signing_key:
|
202
|
+
specification_version: 3
|
203
|
+
summary: A file storing/loading micro-framework.
|
204
|
+
test_files: []
|
205
|
+
|