ccp 0.1.7 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ccp.gemspec +1 -1
- data/lib/ccp.rb +4 -2
- data/lib/ccp/invokers/base.rb +2 -2
- data/lib/ccp/persistent.rb +23 -0
- data/lib/ccp/persistent/base.rb +51 -0
- data/lib/ccp/persistent/dir.rb +48 -0
- data/lib/ccp/persistent/file.rb +59 -0
- data/lib/ccp/persistent/tsv.rb +76 -0
- data/lib/ccp/persistent/versioned.rb +90 -0
- data/lib/ccp/receivers.rb +4 -1
- data/lib/ccp/receivers/base.rb +6 -7
- data/lib/ccp/receivers/commentable.rb +1 -1
- data/lib/ccp/receivers/core.rb +14 -0
- data/lib/ccp/receivers/fixtures.rb +75 -0
- data/lib/ccp/receivers/profileable.rb +1 -1
- data/lib/ccp/receivers/settings.rb +21 -0
- data/lib/ccp/receivers/variables.rb +21 -0
- data/lib/ccp/serializers.rb +19 -0
- data/lib/ccp/serializers/core.rb +13 -0
- data/lib/ccp/serializers/json.rb +15 -0
- data/lib/ccp/serializers/yaml.rb +15 -0
- data/lib/ccp/utils.rb +6 -0
- data/lib/ccp/utils/colorize.rb +15 -0
- data/lib/ccp/version.rb +1 -1
- data/spec/{commands_base_spec.rb → commands/base_spec.rb} +0 -0
- data/spec/{commands_composite_spec.rb → commands/composite_spec.rb} +0 -0
- data/spec/{commands_core_spec.rb → commands/core_spec.rb} +0 -0
- data/spec/{commands_executable_spec.rb → commands/executable_spec.rb} +0 -0
- data/spec/{invokers_spec.rb → invokers/base_spec.rb} +0 -0
- data/spec/persistent/base_spec.rb +49 -0
- data/spec/persistent/dir_spec.rb +111 -0
- data/spec/persistent/file_spec.rb +124 -0
- data/spec/persistent/tsv_spec.rb +19 -0
- data/spec/persistent/versioned_spec.rb +110 -0
- data/spec/receivers/fixture_save_spec.rb +79 -0
- data/spec/serializers/core_spec.rb +19 -0
- data/spec/serializers/json_spec.rb +27 -0
- data/spec/serializers/spec.rb +39 -0
- data/spec/serializers/yaml_spec.rb +27 -0
- metadata +39 -18
- data/lib/ccp/colorize.rb +0 -13
- data/lib/ccp/data.rb +0 -37
- data/lib/ccp/receivers/save_fixture.rb +0 -45
- data/spec/data_spec.rb +0 -18
- data/spec/save_fixture_spec.rb +0 -47
data/ccp.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_dependency "typed", ">= 0.1
|
21
|
+
s.add_dependency "typed", ">= 0.2.1"
|
22
22
|
s.add_dependency "must", ">= 0.2.7"
|
23
23
|
s.add_dependency "dsl_accessor", ">= 0.4.0"
|
24
24
|
|
data/lib/ccp.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
require "typed"
|
2
|
+
require "pathname"
|
2
3
|
require "dsl_accessor"
|
3
4
|
require "active_support/core_ext"
|
4
5
|
require "ccp/version"
|
5
|
-
require "ccp/data"
|
6
6
|
|
7
7
|
module Ccp
|
8
|
-
autoload :
|
8
|
+
autoload :Utils, 'ccp/utils'
|
9
9
|
autoload :Fixtures, 'ccp/fixtures'
|
10
10
|
autoload :Commands, 'ccp/commands'
|
11
11
|
autoload :Invokers, 'ccp/invokers'
|
12
12
|
autoload :Receivers, 'ccp/receivers'
|
13
|
+
autoload :Persistent, 'ccp/persistent'
|
14
|
+
autoload :Serializers, 'ccp/serializers'
|
13
15
|
end
|
data/lib/ccp/invokers/base.rb
CHANGED
@@ -29,8 +29,8 @@ module Ccp
|
|
29
29
|
|
30
30
|
def initialize(options = {})
|
31
31
|
self.receiver = options.delete(:receiver) || self.class.receiver.new
|
32
|
-
receiver.data.
|
33
|
-
receiver.data.merge(options)
|
32
|
+
receiver.data.default.merge!(self.class.default_options)
|
33
|
+
receiver.data.merge!(options)
|
34
34
|
end
|
35
35
|
|
36
36
|
def after
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ccp::Persistent
|
2
|
+
NotFound = Class.new(RuntimeError)
|
3
|
+
|
4
|
+
autoload :Base , 'ccp/persistent/base'
|
5
|
+
autoload :Dir , 'ccp/persistent/dir'
|
6
|
+
autoload :File , 'ccp/persistent/file'
|
7
|
+
autoload :Json , 'ccp/persistent/json'
|
8
|
+
autoload :Tsv , 'ccp/persistent/tsv'
|
9
|
+
autoload :Versioned, 'ccp/persistent/versioned'
|
10
|
+
|
11
|
+
def self.lookup(name)
|
12
|
+
case name.to_s
|
13
|
+
when "dir" ; Ccp::Persistent::Dir
|
14
|
+
when "tsv" ; Ccp::Persistent::Tsv
|
15
|
+
when "file" ; Ccp::Persistent::File
|
16
|
+
when "json" ; Ccp::Persistent::Json
|
17
|
+
else
|
18
|
+
name.must(Ccp::Persistent::Base) {
|
19
|
+
raise NotFound, "%s: %s" % [name.class, name]
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ccp::Persistent::Base
|
4
|
+
attr_reader :source
|
5
|
+
attr_reader :serializer
|
6
|
+
|
7
|
+
delegate :ext, :encode, :decode, :to => :serializer
|
8
|
+
|
9
|
+
def initialize(source, serializer)
|
10
|
+
@source = source
|
11
|
+
@serializer = Ccp::Serializers.lookup(serializer)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.ext
|
15
|
+
raise NotImplementedError, "subclass resposibility"
|
16
|
+
end
|
17
|
+
|
18
|
+
def exist?(key)
|
19
|
+
raise NotImplementedError, "subclass resposibility"
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(hash)
|
23
|
+
hash.keys.each do |key|
|
24
|
+
self[key] = hash[key]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def load!(key)
|
29
|
+
raise NotImplementedError, "subclass resposibility"
|
30
|
+
end
|
31
|
+
|
32
|
+
def load(key)
|
33
|
+
raise NotImplementedError, "subclass resposibility"
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
load!(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
def []=(key, val)
|
41
|
+
raise NotImplementedError, "subclass resposibility"
|
42
|
+
end
|
43
|
+
|
44
|
+
def keys
|
45
|
+
raise NotImplementedError, "subclass resposibility"
|
46
|
+
end
|
47
|
+
|
48
|
+
def truncate
|
49
|
+
raise NotImplementedError, "subclass resposibility"
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ccp::Persistent::Dir < Ccp::Persistent::Base
|
4
|
+
def self.ext
|
5
|
+
""
|
6
|
+
end
|
7
|
+
|
8
|
+
def exist?(key)
|
9
|
+
path_for(key).exist?
|
10
|
+
end
|
11
|
+
|
12
|
+
def load!(key)
|
13
|
+
path = path_for(key)
|
14
|
+
if path.exist?
|
15
|
+
decode(path.read{})
|
16
|
+
else
|
17
|
+
raise Ccp::Persistent::NotFound, key.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def load(key)
|
22
|
+
load!(key)
|
23
|
+
rescue Ccp::Persistent::NotFound
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(key, val)
|
28
|
+
path_for(key).open("w+"){|f| f.print encode(val)}
|
29
|
+
end
|
30
|
+
|
31
|
+
def keys
|
32
|
+
Dir["#{path}/*.#{ext}"].map{|i| File.basename(i, ".*")}.sort
|
33
|
+
end
|
34
|
+
|
35
|
+
def truncate
|
36
|
+
Dir["#{path}/*.#{ext}"].each{|file| File.unlink(file)}
|
37
|
+
end
|
38
|
+
|
39
|
+
def path
|
40
|
+
@path ||= Pathname(@source)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def path_for(key, mkdir = true)
|
45
|
+
path.mkpath if mkdir
|
46
|
+
path + "#{key}.#{ext}"
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ccp::Persistent::File < Ccp::Persistent::Base
|
4
|
+
def self.ext
|
5
|
+
""
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(source, serializer)
|
9
|
+
@serializer = Ccp::Serializers.lookup(serializer)
|
10
|
+
@source = File.extname(source) == ".#{ext}" ? source : "#{source}.#{ext}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def exist?(key)
|
14
|
+
read_data.has_key?(key.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def load!(key)
|
18
|
+
hash = read_data
|
19
|
+
if hash.has_key?(key.to_s)
|
20
|
+
read_data[key.to_s]
|
21
|
+
else
|
22
|
+
raise Ccp::Persistent::NotFound, key.to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def load(key)
|
27
|
+
load!(key)
|
28
|
+
rescue Ccp::Persistent::NotFound
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def []=(key, val)
|
33
|
+
hash = read_data
|
34
|
+
hash[key.to_s] = val
|
35
|
+
raw_write(encode(hash))
|
36
|
+
end
|
37
|
+
|
38
|
+
def keys
|
39
|
+
read_data.keys.sort
|
40
|
+
end
|
41
|
+
|
42
|
+
def truncate
|
43
|
+
File.unlink(path.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
def path
|
47
|
+
@path ||= Pathname(@source)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def read_data
|
52
|
+
path.exist? ? decode(path.read{}).must(Hash) : {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def raw_write(buf)
|
56
|
+
path.parent.mkpath
|
57
|
+
path.open("w+"){|f| f.print buf}
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ccp::Persistent::Tsv < Ccp::Persistent::Dir
|
4
|
+
def self.ext
|
5
|
+
"tsv"
|
6
|
+
end
|
7
|
+
|
8
|
+
def load_tsv(path)
|
9
|
+
hash = {}
|
10
|
+
path.readlines.each_with_index do |line, i|
|
11
|
+
no = i+1
|
12
|
+
key, val = line.split(/\t/,2)
|
13
|
+
unless val
|
14
|
+
$stderr.puts "#{self.class}#load_tsv: value not found. key='#{key}' (line: #{no})"
|
15
|
+
next
|
16
|
+
end
|
17
|
+
obj = decode(val)
|
18
|
+
hash[key] = obj
|
19
|
+
end
|
20
|
+
|
21
|
+
return hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def save_tsv(f, hash)
|
25
|
+
keys = hash.keys
|
26
|
+
keys =
|
27
|
+
case keys.first
|
28
|
+
when NilClass ; return
|
29
|
+
when Symbol ; keys
|
30
|
+
when /\A\d+\Z/; keys.sort_by(&:to_i)
|
31
|
+
when String ; keys.sort
|
32
|
+
else ; keys
|
33
|
+
end
|
34
|
+
|
35
|
+
keys.each do |key|
|
36
|
+
f.puts "%s\t%s\n" % [key, encode(hash[key])]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def load!(key)
|
41
|
+
path = path_for(key)
|
42
|
+
if path.exist?
|
43
|
+
super
|
44
|
+
elsif (tsv = tsv_path_for(key)).exist?
|
45
|
+
load_tsv(tsv)
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def []=(key, val)
|
52
|
+
# Now, tsv can manage only hash
|
53
|
+
case val
|
54
|
+
when Hash
|
55
|
+
tsv_path_for(key).open("w+"){|f| save_tsv(f, val)}
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def files
|
62
|
+
Dir["#{path}/*.#{ext}"] + Dir["#{path}/*.#{ext}.#{self.class.ext}"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def keys
|
66
|
+
files.map{|i| File.basename(i).split(".").first}.sort
|
67
|
+
end
|
68
|
+
|
69
|
+
def truncate
|
70
|
+
files.each{|file| File.unlink(file)}
|
71
|
+
end
|
72
|
+
|
73
|
+
def tsv_path_for(key)
|
74
|
+
Pathname(path_for(key).to_s + ".tsv")
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Ccp::Persistent::Versioned
|
4
|
+
attr_reader :path
|
5
|
+
|
6
|
+
Storage = Struct.new(:dir, :name, :ext, :kvs)
|
7
|
+
class Storage
|
8
|
+
def self.complete(file, default_dir, default_kvs, default_ext)
|
9
|
+
s = file.must(Storage) {
|
10
|
+
path, ext, kvs = file.to_s.split(".", 3)
|
11
|
+
new(default_dir, path, ext, kvs)
|
12
|
+
}
|
13
|
+
|
14
|
+
s.name = Pathname(s.name).basename(".*").to_s
|
15
|
+
s.dir = default_dir if s.dir.blank?
|
16
|
+
s.ext = default_ext if s.ext.blank?
|
17
|
+
s.kvs = default_kvs if s.kvs.blank?
|
18
|
+
|
19
|
+
s.ext.must.not.blank
|
20
|
+
s.kvs.must.not.blank
|
21
|
+
|
22
|
+
return s
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
kvs = Ccp::Persistent.lookup(self.kvs)
|
27
|
+
ext = Ccp::Serializers.lookup(self.ext)
|
28
|
+
base = [name.to_s, ext.ext, kvs.ext].join(".").sub(/\.$/,'')
|
29
|
+
return Pathname(dir) + base
|
30
|
+
end
|
31
|
+
|
32
|
+
def create
|
33
|
+
kvs = Ccp::Persistent.lookup(self.kvs)
|
34
|
+
ext = Ccp::Serializers.lookup(self.ext)
|
35
|
+
kvs.new(path, ext)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module StorageScanner
|
40
|
+
def scan(dir)
|
41
|
+
files = Dir.chdir(dir){
|
42
|
+
Dir["*"].grep(/^(\d+)\./).sort{|a,b|
|
43
|
+
[a.to_i, File.mtime(a)] <=> [b.to_i, File.mtime(b)]
|
44
|
+
}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
extend self
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(dir, options = {})
|
52
|
+
@path = Pathname(dir)
|
53
|
+
@default_kvs = options[:kvs] || :dir
|
54
|
+
@default_ext = options[:ext] || :json
|
55
|
+
@storages = {}
|
56
|
+
|
57
|
+
@path.mkpath
|
58
|
+
end
|
59
|
+
|
60
|
+
# 最新のストレージを返す。存在しなければnil
|
61
|
+
def latest
|
62
|
+
storage = StorageScanner.scan(path).last
|
63
|
+
storage ? self[storage] : nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# 最新のストレージを返す。存在しなければ例外
|
67
|
+
def latest!
|
68
|
+
latest.must.exist { raise Ccp::Persistent::NotFound, "#{path}/*" }
|
69
|
+
end
|
70
|
+
|
71
|
+
# 最新のストレージを返す。存在しなければ作成
|
72
|
+
def default
|
73
|
+
latest || now
|
74
|
+
end
|
75
|
+
|
76
|
+
# 現在の時刻で新しいストレージを作成して返す
|
77
|
+
def now
|
78
|
+
self[Time.now.to_i]
|
79
|
+
end
|
80
|
+
|
81
|
+
# 指定したストレージを返す。存在しなければ作成して返す
|
82
|
+
def [](key)
|
83
|
+
storage = Storage.complete(key, path, @default_kvs, @default_ext)
|
84
|
+
@storages[storage.to_s] ||= storage.create
|
85
|
+
end
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
"<Kvs::Versioned dir=#{path} kvs=#{@default_kvs} ext=#{@default_ext}>"
|
89
|
+
end
|
90
|
+
end
|
data/lib/ccp/receivers.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
module Ccp
|
2
2
|
module Receivers
|
3
|
+
autoload :Core , 'ccp/receivers/core'
|
3
4
|
autoload :Base , 'ccp/receivers/base'
|
4
5
|
autoload :Global , 'ccp/receivers/global'
|
6
|
+
autoload :Settings , 'ccp/receivers/settings'
|
7
|
+
autoload :Variables , 'ccp/receivers/variables'
|
5
8
|
autoload :Executable , 'ccp/receivers/executable'
|
6
9
|
autoload :Aroundable , 'ccp/receivers/aroundable'
|
7
10
|
autoload :Commentable , 'ccp/receivers/commentable'
|
8
11
|
autoload :Profileable , 'ccp/receivers/profileable'
|
9
|
-
autoload :
|
12
|
+
autoload :Fixtures , 'ccp/receivers/fixtures'
|
10
13
|
end
|
11
14
|
end
|
data/lib/ccp/receivers/base.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
module Ccp
|
2
2
|
module Receivers
|
3
3
|
class Base
|
4
|
-
include
|
4
|
+
include Core
|
5
|
+
include Settings
|
6
|
+
include Variables
|
5
7
|
include Commentable
|
6
8
|
|
7
9
|
# for execute
|
8
10
|
include Executable
|
9
11
|
include Profileable
|
10
12
|
include Aroundable
|
11
|
-
include
|
13
|
+
include Fixtures
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
"#<Receivers::#{klass_name} data:#{data.inspect}>"
|
17
|
-
end
|
15
|
+
# ensure to call '#setup' for module initializations
|
16
|
+
def self.new(*args) r = super; r.setup; r; end
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|