simple_file_store 0.0.4
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/.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
|
+
|