ccp 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/ccp.gemspec +1 -1
  2. data/lib/ccp.rb +4 -2
  3. data/lib/ccp/invokers/base.rb +2 -2
  4. data/lib/ccp/persistent.rb +23 -0
  5. data/lib/ccp/persistent/base.rb +51 -0
  6. data/lib/ccp/persistent/dir.rb +48 -0
  7. data/lib/ccp/persistent/file.rb +59 -0
  8. data/lib/ccp/persistent/tsv.rb +76 -0
  9. data/lib/ccp/persistent/versioned.rb +90 -0
  10. data/lib/ccp/receivers.rb +4 -1
  11. data/lib/ccp/receivers/base.rb +6 -7
  12. data/lib/ccp/receivers/commentable.rb +1 -1
  13. data/lib/ccp/receivers/core.rb +14 -0
  14. data/lib/ccp/receivers/fixtures.rb +75 -0
  15. data/lib/ccp/receivers/profileable.rb +1 -1
  16. data/lib/ccp/receivers/settings.rb +21 -0
  17. data/lib/ccp/receivers/variables.rb +21 -0
  18. data/lib/ccp/serializers.rb +19 -0
  19. data/lib/ccp/serializers/core.rb +13 -0
  20. data/lib/ccp/serializers/json.rb +15 -0
  21. data/lib/ccp/serializers/yaml.rb +15 -0
  22. data/lib/ccp/utils.rb +6 -0
  23. data/lib/ccp/utils/colorize.rb +15 -0
  24. data/lib/ccp/version.rb +1 -1
  25. data/spec/{commands_base_spec.rb → commands/base_spec.rb} +0 -0
  26. data/spec/{commands_composite_spec.rb → commands/composite_spec.rb} +0 -0
  27. data/spec/{commands_core_spec.rb → commands/core_spec.rb} +0 -0
  28. data/spec/{commands_executable_spec.rb → commands/executable_spec.rb} +0 -0
  29. data/spec/{invokers_spec.rb → invokers/base_spec.rb} +0 -0
  30. data/spec/persistent/base_spec.rb +49 -0
  31. data/spec/persistent/dir_spec.rb +111 -0
  32. data/spec/persistent/file_spec.rb +124 -0
  33. data/spec/persistent/tsv_spec.rb +19 -0
  34. data/spec/persistent/versioned_spec.rb +110 -0
  35. data/spec/receivers/fixture_save_spec.rb +79 -0
  36. data/spec/serializers/core_spec.rb +19 -0
  37. data/spec/serializers/json_spec.rb +27 -0
  38. data/spec/serializers/spec.rb +39 -0
  39. data/spec/serializers/yaml_spec.rb +27 -0
  40. metadata +39 -18
  41. data/lib/ccp/colorize.rb +0 -13
  42. data/lib/ccp/data.rb +0 -37
  43. data/lib/ccp/receivers/save_fixture.rb +0 -45
  44. data/spec/data_spec.rb +0 -18
  45. 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.5"
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 :Colorize, 'ccp/colorize'
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
@@ -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.merge_default(self.class.default_options)
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 :SaveFixture , 'ccp/receivers/save_fixture'
12
+ autoload :Fixtures , 'ccp/receivers/fixtures'
10
13
  end
11
14
  end
@@ -1,20 +1,19 @@
1
1
  module Ccp
2
2
  module Receivers
3
3
  class Base
4
- include Ccp::Data
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 SaveFixture
13
+ include Fixtures
12
14
 
13
- def inspect
14
- klass_name = self.class.name.to_s.split(/::/).last
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
@@ -4,7 +4,7 @@ module Ccp
4
4
 
5
5
  Comment = Struct.new(:text, :level)
6
6
  class Comment
7
- include Colorize
7
+ include Utils::Colorize
8
8
  def colorized
9
9
  case level
10
10
  when :warn ; yellow(text)