basilik 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,141 @@
1
+ require 'basilik/faults'
2
+
3
+ module Basilik
4
+ module Action
5
+ include Basilik::Faults
6
+
7
+ def read
8
+ location = json_url
9
+ resp = Typhoeus.get( location, gen_opts( params: {format: :export} ) )
10
+ res = handle_response( resp, location )
11
+ res.is_a?(Map) ? Snapshot.new( res ).to_map : res.is_a?(String) ? res.to_val : res
12
+ end
13
+
14
+ def set( data )
15
+ location = json_url
16
+ resp = Typhoeus.put( location, gen_opts( body: data.to_json ) )
17
+ handle_response( resp, location )
18
+ end
19
+
20
+ def update( data )
21
+ location = json_url
22
+ resp = Typhoeus.patch( location, gen_opts( body: data.to_json ) )
23
+ handle_response( resp, location )
24
+ end
25
+
26
+ def inc( field )
27
+ map = read
28
+ current_val = map[field]
29
+ raise NonNumericFieldError, "The field #{field} does not have a numeric value" if current_val and !current_val.is_a? Numeric
30
+ if current_val
31
+ new_val = map[field] + 1
32
+ else
33
+ new_val = 0
34
+ end
35
+ update( field => new_val )
36
+ end
37
+
38
+ def dec( field )
39
+ map = read
40
+ current_val = map[field]
41
+ raise NonNumericFieldError, "The field #{field} does not have a numeric value" if current_val and !current_val.is_a? Numeric
42
+ if current_val
43
+ new_val = map[field] - 1
44
+ else
45
+ new_val = 0
46
+ end
47
+ update( field => new_val )
48
+ end
49
+
50
+ def remove
51
+ location = json_url
52
+ resp = Typhoeus.delete( location, gen_opts )
53
+ unless resp.success?
54
+ raise InvalidRequestError, "<#{resp.return_code}> Unable to perform request #{location}"
55
+ end
56
+ end
57
+
58
+ def push( data=nil )
59
+ location = json_url
60
+ opts = {}
61
+ if data
62
+ opts[:body] = data.to_json
63
+ else
64
+ opts[:body] = ''.to_json
65
+ end
66
+ resp = Typhoeus.post( location, gen_opts( opts ) )
67
+ res = handle_response( resp, location )
68
+ Basilik::Load.new( uri.to_s + '/' + res.name )
69
+ end
70
+
71
+ def set_priority( priority )
72
+ location = json_url( true )
73
+ resp = Typhoeus.put( location, gen_opts( body: priority.to_json ) )
74
+ unless resp.success? or resp.body
75
+ raise InvalidRequestError, "<#{resp.return_code}> Unable to perform request #{location}"
76
+ end
77
+ end
78
+
79
+ def get_priority
80
+ location = json_url( true )
81
+ resp = Typhoeus.get( location, gen_opts( params: { format: :export} ) )
82
+ res = handle_response( resp, location )
83
+ res.to_i
84
+ rescue NoDataError
85
+ nil
86
+ end
87
+
88
+ def get_rules
89
+ location = rules_url
90
+ resp = Typhoeus.get( location, gen_opts )
91
+ handle_response( resp, location )
92
+ end
93
+
94
+ def set_rules( rules )
95
+ location = rules_url
96
+ resp = Typhoeus.put( location, gen_opts( body: {:rules => rules}.to_json ) )
97
+ handle_response( resp, location )
98
+ end
99
+
100
+
101
+ private
102
+
103
+ def gen_opts( opts={} )
104
+ if @auth_token
105
+ opts[:params] ||= {}
106
+ opts[:params][:auth] = @auth_token
107
+ end
108
+ opts
109
+ end
110
+
111
+ def handle_response( resp, location )
112
+ if resp.response_code == 403
113
+ raise PermissionDeniedError, "No permission for #{location}"
114
+ end
115
+ unless resp.success?
116
+ raise InvalidRequestError, "<#{resp.return_code}> Unable to perform request #{location}"
117
+ end
118
+ if resp.body.empty? or resp.body == "null"
119
+ raise NoDataError, "No data found at location #{location}"
120
+ end
121
+
122
+ results = JSON.parse( resp.body ) rescue nil
123
+ results ? (results.is_a?(Hash) ? Map( results ) : results ) : resp.body.gsub( /\"/, '' )
124
+ end
125
+
126
+ def json_url( priority=nil )
127
+ if @url =~ /\.json$/
128
+ loc = @url
129
+ elsif root?
130
+ loc = @url + "/.json"
131
+ else
132
+ loc = @url + ".json"
133
+ end
134
+ priority ? loc.gsub( /\.json$/, "/.priority/.json" ) : loc
135
+ end
136
+
137
+ def rules_url
138
+ @uri.merge( '.settings/rules.json' ).to_s
139
+ end
140
+ end
141
+ end
@@ -0,0 +1 @@
1
+ Basilik.require_all_libs_relative_to File.expand_path( File.join( %w(basilik core_ext), Basilik::LIBPATH ) )
@@ -0,0 +1,23 @@
1
+ require 'values'
2
+
3
+ class Map
4
+ Event = Value.new( :event_type, :ref )
5
+ def diff(other)
6
+ res = dup.
7
+ delete_if { |k, v| other[k] == v }.
8
+ merge!(other.dup.delete_if { |k, v| has_key?(k) })
9
+ events = []
10
+ res.each_pair do |k,v|
11
+ if self[k]
12
+ if other[k]
13
+ events << Event.new( Basilik::Load.evt_value, k )
14
+ else
15
+ events << Event.new( Basilik::Load.evt_child_removed, k )
16
+ end
17
+ else
18
+ events << Event.new( Basilik::Load.evt_child_added, k )
19
+ end
20
+ end
21
+ events
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ class String
2
+ def to_val
3
+ if self =~ /\A[-+]?\d+$/
4
+ Integer( self )
5
+ elsif self =~ /\A[-+]?\d+\.\d+$/
6
+ Float( self )
7
+ elsif self =~ /false/
8
+ false
9
+ elsif self =~ /true/
10
+ true
11
+ else
12
+ self
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Basilik
2
+ module Faults
3
+ class NoDataError < RuntimeError; end
4
+ class InvalidRequestError < RuntimeError; end
5
+ class InvalidJSONError < RuntimeError; end
6
+ class PermissionDeniedError < RuntimeError; end
7
+ class NonNumericFieldError < RuntimeError; end
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ require 'basilik/ref'
2
+
3
+ module Basilik
4
+ class Load
5
+ include Basilik::Ref
6
+ include Basilik::Action
7
+
8
+ attr_reader :url, :uri
9
+
10
+ def self.evt_value; :value; end
11
+ def self.evt_child_added; :add_child; end
12
+ def self.evt_child_changed; :mod_child; end
13
+ def self.evt_child_removed; :rm_child; end
14
+ def self.evt_child_moved; :mv_child; end
15
+
16
+ def initialize( url, auth_token=nil )
17
+ @url = url
18
+ @uri = URI.parse( @url )
19
+ @auth_token = auth_token
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,64 @@
1
+ require 'uri'
2
+
3
+ module Basilik
4
+ module Ref
5
+ def name
6
+ parse_path[-1]
7
+ end
8
+
9
+ def val
10
+ read
11
+ end
12
+
13
+ def root
14
+ return self if root?
15
+ Basilik::Load.new( @uri.scheme + '://' + @uri.host )
16
+ end
17
+
18
+ def root?
19
+ @uri.path.empty? or @uri.path == "/"
20
+ end
21
+
22
+ def parent
23
+ return nil if root?
24
+ path = parse_path[0..-2].join( "/" )
25
+ Basilik::Load.new( root.uri.merge(path).to_s )
26
+ end
27
+
28
+ def child( child_path )
29
+ Basilik::Load.new( "#{uri.to_s}/#{child_path}" )
30
+ end
31
+
32
+ def child?( child_path )
33
+ child( child_path ).read
34
+ true
35
+ rescue
36
+ false
37
+ end
38
+
39
+ def children?
40
+ data = read
41
+ data.is_a? Map and !data.empty?
42
+ end
43
+
44
+ def num_children
45
+ data = read
46
+ if data.is_a? Map and !data.empty?
47
+ data.count
48
+ else
49
+ 0
50
+ end
51
+ end
52
+
53
+ def to_s
54
+ @url
55
+ end
56
+
57
+
58
+ private
59
+
60
+ def parse_path
61
+ @uri.path.split( "/" )
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,99 @@
1
+ module Basilik
2
+ class Snapshot
3
+
4
+ class Node
5
+ attr_accessor :priority, :name, :parent, :children, :value
6
+
7
+ def initialize( name )
8
+ @name = name
9
+ @priority = nil
10
+ @children = []
11
+ @value = nil
12
+ @parent = nil
13
+ end
14
+
15
+ def add_child( child )
16
+ child.parent = self
17
+ children << child
18
+ end
19
+
20
+ def to_map
21
+ map = Map.new
22
+ order( map )
23
+ map
24
+ end
25
+
26
+ def dump
27
+ puts "#{' '*level}#{name} [#{priority.inspect}] --#{value.inspect}"
28
+ children.sort_by do |c|
29
+ if c.priority and c.priority.is_a? Integer
30
+ "b_#{c.priority}_#{c.name}"
31
+ elsif c.priority
32
+ "z_#{c.priority}_#{c.name}"
33
+ else
34
+ "a_#{c.name}"
35
+ end
36
+ end.each do |c|
37
+ c.dump
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def order( map )
44
+ map[name] = children.empty? ? value : Map.new
45
+ children.sort_by do |c|
46
+ if c.priority and c.priority.is_a? Numeric
47
+ "b_#{'%05.2f' % c.priority}_#{c.name}"
48
+ elsif c.priority
49
+ "z_#{c.priority}_#{c.name}"
50
+ else
51
+ "a_#{c.name}"
52
+ end
53
+ end.each do |c|
54
+ c.send( :order, map[name] )
55
+ end
56
+ end
57
+
58
+ def level
59
+ level = 0
60
+ node = self
61
+ while node.parent do
62
+ level +=1
63
+ node = node.parent
64
+ end
65
+ level
66
+ end
67
+ end
68
+
69
+ def initialize( map )
70
+ @map = map
71
+ @root = Node.new( 'root' )
72
+ build( @map, @root )
73
+ end
74
+
75
+ def to_map
76
+ @root.to_map.root
77
+ end
78
+
79
+ def dump
80
+ @root.dump
81
+ end
82
+
83
+ private
84
+
85
+ def build( map, root )
86
+ map.each_pair do |k,v|
87
+ node = Node.new( k )
88
+ if v.is_a? Hash
89
+ priority = v.delete('.priority')
90
+ node.priority = priority if priority
91
+ build( v, node )
92
+ else
93
+ node.value = v.is_a?(String) ? v.to_val : v
94
+ end
95
+ root.add_child( node )
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module Basilik
2
+ VERSION = "0.0.1"
3
+ end
data/lib/basilik.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+ require 'typhoeus'
3
+ require 'map'
4
+
5
+ module Basilik
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+
9
+ def self.require_all_libs_relative_to( fname, dir = nil )
10
+ dir ||= ::File.basename(fname, '.*')
11
+ search_me = ::File.expand_path(
12
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
13
+ Dir.glob(search_me).sort.each { |rb| require rb }
14
+ end
15
+ end
16
+
17
+ Basilik.require_all_libs_relative_to File.expand_path( "basilik", Basilik::LIBPATH )
Binary file
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Map do
4
+ describe '#diff' do
5
+ describe 'single vals' do
6
+ it "tracks value changes correctly" do
7
+ prev = Map(:a,1)
8
+ curr = Map(:a,2)
9
+ evts = prev.diff( curr )
10
+ evts.should have(1).item
11
+ evts.first.event_type.should == Basilik::Load.evt_value
12
+ evts.first.ref.should == 'a'
13
+ end
14
+
15
+ it "tracks adds correctly" do
16
+ prev = Map.new
17
+ curr = Map(:a,2)
18
+ evts = prev.diff( curr )
19
+ evts.should have(1).item
20
+ evts.first.event_type.should == Basilik::Load.evt_child_added
21
+ evts.first.ref.should == 'a'
22
+ end
23
+
24
+ it "tracks deletes correctly" do
25
+ prev = Map(:a,2)
26
+ curr = Map.new
27
+ evts = prev.diff( curr )
28
+ evts.should have(1).item
29
+ evts.first.event_type.should == Basilik::Load.evt_child_removed
30
+ evts.first.ref.should == 'a'
31
+ end
32
+ end
33
+
34
+ describe 'multi vals' do
35
+ it 'tracks multi value changes correctly' do
36
+ prev = Map(:a,1)
37
+ curr = Map({a:{a_1:10, a_2:20}})
38
+ evts = prev.diff( curr )
39
+ evts.should have(1).item
40
+ evts.first.event_type.should == Basilik::Load.evt_value
41
+ evts.first.ref.should == 'a'
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe String do
4
+ describe '#to_val' do
5
+ it "keep strings unchanged" do
6
+ "fred".to_val.should == "fred"
7
+ end
8
+
9
+ it "converts an integer correctly" do
10
+ "10".to_val.should == 10
11
+ end
12
+
13
+ it "converts an float correctly" do
14
+ "10.5".to_val.should == 10.5
15
+ end
16
+
17
+ it "converts a boolean correctly" do
18
+ "true".to_val.should == true
19
+ "false".to_val.should == false
20
+ end
21
+
22
+ it 'converts null correctly' do
23
+ 'null'.to_val.should == "null"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ describe Basilik::Ref do
4
+ before :all do
5
+ @ref = Basilik::Load.new( ENV['fb_url'] )
6
+ end
7
+
8
+ describe '#child?' do
9
+ it "retrieves complex values correctly" do
10
+ @ref.set( a:{b:{c:1, d:"hello"}} )
11
+ @ref.child?( :a ).should == true
12
+ @ref.child?( 'a/b' ).should == true
13
+ @ref.child?( :z ).should == false
14
+ end
15
+ end
16
+
17
+ describe '#children?' do
18
+ it "retrieves complex values correctly" do
19
+ @ref.set( a:{b:{c:1, d:"hello"}} )
20
+ @ref.child(:a).children?.should == true
21
+ @ref.child('a/b').children?.should == true
22
+ @ref.child( 'a/b/c' ).children?.should == false
23
+ end
24
+ end
25
+
26
+ describe '#num_children' do
27
+ it "retrieves complex values correctly" do
28
+ @ref.set( a:{b:{c:1, d:"hello"}} )
29
+ @ref.child(:a).num_children.should == 1
30
+ @ref.child('a/b').num_children.should == 2
31
+ @ref.child( 'a/b/c' ).num_children.should == 0
32
+ end
33
+ end
34
+
35
+ describe '#val' do
36
+ it "retrieves simple value correctly" do
37
+ @ref.set( a:1 )
38
+ @ref.child( :a ).val.to_i.should == 1
39
+ end
40
+
41
+ it "retrieves complex values correctly" do
42
+ @ref.set( a:{b:{c:1, d:"hello"}} )
43
+ @ref.child( :a ).val.should == {b:{c:1, d:"hello"}}
44
+ end
45
+ end
46
+
47
+ describe '#child' do
48
+ it "creates a child ref correctly" do
49
+ child_ref = @ref.child( 'fred')
50
+ child_ref.name.should == 'fred'
51
+ child_ref.parent.to_s.should == ENV['fb_url']
52
+ end
53
+
54
+ it "creates a deep child ref correctly" do
55
+ child_ref = @ref.child( 'fred/blee/duh')
56
+ child_ref.name.should == 'duh'
57
+ child_ref.parent.to_s.should == ENV['fb_url'] + '/fred/blee'
58
+ end
59
+ end
60
+
61
+ describe '#name' do
62
+ it "identifies / as the root name correctly" do
63
+ @ref.name.should be_nil
64
+ end
65
+
66
+ it "identifies a url name correctly" do
67
+ ref = Basilik::Load.new( ENV['fb_url'] + '/fred/blee' )
68
+ ref.name.should == 'blee'
69
+ end
70
+ end
71
+
72
+ describe '#root' do
73
+ it "identifies / as root correctly" do
74
+ @ref.root.should == @ref
75
+ end
76
+
77
+ it "identifies non root url correctly" do
78
+ ref = Basilik::Load.new( ENV['fb_url'] + '/fred/blee' )
79
+ ref.root.to_s.should == ENV['fb_url']
80
+ end
81
+ end
82
+
83
+ describe '#root?' do
84
+ it "identifies root correctly" do
85
+ @ref.should be_root
86
+ end
87
+
88
+ it "identifies / as root correctly" do
89
+ ref = Basilik::Load.new( ENV['fb_url'] + '/' )
90
+ ref.should be_root
91
+ end
92
+
93
+ it "identifies non root correctly" do
94
+ ref = Basilik::Load.new( ENV['fb_url'] + '/fred' )
95
+ ref.should_not be_root
96
+ end
97
+ end
98
+
99
+ describe '#parent' do
100
+ it "identifies parent from root correctly" do
101
+ @ref.parent.should be_nil
102
+ end
103
+
104
+ it "identifies a parent correctly" do
105
+ ref = Basilik::Load.new( ENV['fb_url'] + '/fred/blee' )
106
+ ref.parent.to_s.should == ENV['fb_url'] + '/fred'
107
+ end
108
+ end
109
+ end