adamsalter-ken 0.1.1

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/lib/ken/logger.rb ADDED
@@ -0,0 +1,233 @@
1
+ require "time"
2
+
3
+ # ==== Public Ken Logger API
4
+ #
5
+ # Logger taken from Merb/Datamapper :)
6
+ #
7
+ # To replace an existing logger with a new one:
8
+ # Ken.logger.set_log(log{String, IO},level{Symbol, String})
9
+ #
10
+ # Available logging levels are:
11
+ # :off, :fatal, :error, :warn, :info, :debug
12
+ #
13
+ # Logging via:
14
+ # Ken.logger.fatal(message<String>)
15
+ # Ken.logger.error(message<String>)
16
+ # Ken.logger.warn(message<String>)
17
+ # Ken.logger.info(message<String>)
18
+ # Ken.logger.debug(message<String>)
19
+ #
20
+ # Flush the buffer to
21
+ # Ken.logger.flush
22
+ #
23
+ # Remove the current log object
24
+ # Ken.logger.close
25
+ #
26
+ # ==== Private Ken Logger API
27
+ #
28
+ # To initialize the logger you create a new object, proxies to set_log.
29
+ # ken::Logger.new(log{String, IO}, level{Symbol, String})
30
+ #
31
+ # Logger will not create the file until something is actually logged
32
+ # This avoids file creation on Ken init when it creates the
33
+ # default logger.
34
+ module Ken
35
+
36
+ class << self #:nodoc:
37
+ attr_accessor :logger
38
+ end
39
+
40
+ class Logger
41
+ attr_accessor :aio
42
+ attr_accessor :delimiter
43
+ attr_reader :level
44
+ attr_reader :buffer
45
+ attr_reader :log
46
+
47
+ # @note
48
+ # Ruby (standard) logger levels:
49
+ # off: absolutely nothing
50
+ # fatal: an unhandleable error that results in a program crash
51
+ # error: a handleable error condition
52
+ # warn: a warning
53
+ # info: generic (useful) information about system operation
54
+ # debug: low-level information for developers
55
+ #
56
+ # Ken::Logger::LEVELS[:off, :fatal, :error, :warn, :info, :debug]
57
+
58
+ LEVELS =
59
+ {
60
+ :off => 99999,
61
+ :fatal => 7,
62
+ :error => 6,
63
+ :warn => 4,
64
+ :info => 3,
65
+ :debug => 0
66
+ }
67
+
68
+ def level=(new_level)
69
+ @level = LEVELS[new_level.to_sym]
70
+ reset_methods(:close)
71
+ end
72
+
73
+ private
74
+
75
+ # The idea here is that instead of performing an 'if' conditional check on
76
+ # each logging we do it once when the log object is setup
77
+ def set_write_method
78
+ @log.instance_eval do
79
+
80
+ # Determine if asynchronous IO can be used
81
+ def aio?
82
+ @aio = !RUBY_PLATFORM.match(/java|mswin/) &&
83
+ !(@log == STDOUT) &&
84
+ @log.respond_to?(:write_nonblock)
85
+ end
86
+
87
+ # Define the write method based on if aio an be used
88
+ undef write_method if defined? write_method
89
+ if aio?
90
+ alias :write_method :write_nonblock
91
+ else
92
+ alias :write_method :write
93
+ end
94
+ end
95
+ end
96
+
97
+ def initialize_log(log)
98
+ close if @log # be sure that we don't leave open files laying around.
99
+ @log = log || "log/dm.log"
100
+ end
101
+
102
+ def reset_methods(o_or_c)
103
+ if o_or_c == :open
104
+ alias internal_push push_opened
105
+ elsif o_or_c == :close
106
+ alias internal_push push_closed
107
+ end
108
+ end
109
+
110
+ def push_opened(string)
111
+ message = Time.now.httpdate
112
+ message << delimiter
113
+ message << string
114
+ message << "\n" unless message[-1] == ?\n
115
+ @buffer << message
116
+ flush # Force a flush for now until we figure out where we want to use the buffering.
117
+ end
118
+
119
+ def push_closed(string)
120
+ unless @log.respond_to?(:write)
121
+ log = Pathname(@log)
122
+ log.dirname.mkpath
123
+ @log = log.open('a')
124
+ @log.sync = true
125
+ end
126
+ set_write_method
127
+ reset_methods(:open)
128
+ push(string)
129
+ end
130
+
131
+ alias internal_push push_closed
132
+
133
+ def prep_msg(message, level)
134
+ level << delimiter << message
135
+ end
136
+
137
+ public
138
+
139
+ # To initialize the logger you create a new object, proxies to set_log.
140
+ # Ken::Logger.new(log{String, IO},level{Symbol, String})
141
+ #
142
+ # @param log<IO,String> either an IO object or a name of a logfile.
143
+ # @param log_level<String> the message string to be logged
144
+ # @param delimiter<String> delimiter to use between message sections
145
+ # @param log_creation<Boolean> log that the file is being created
146
+ def initialize(*args)
147
+ set_log(*args)
148
+ end
149
+
150
+ # To replace an existing logger with a new one:
151
+ # Ken.logger.set_log(log{String, IO},level{Symbol, String})
152
+ #
153
+ # @param log<IO,String> either an IO object or a name of a logfile.
154
+ # @param log_level<Symbol> a symbol representing the log level from
155
+ # {:off, :fatal, :error, :warn, :info, :debug}
156
+ # @param delimiter<String> delimiter to use between message sections
157
+ # @param log_creation<Boolean> log that the file is being created
158
+ def set_log(log, log_level = :off, delimiter = " ~ ", log_creation = false)
159
+ delimiter ||= " ~ "
160
+
161
+ if log_level && LEVELS[log_level.to_sym]
162
+ self.level = log_level.to_sym
163
+ else
164
+ self.level = :debug
165
+ end
166
+
167
+ @buffer = []
168
+ @delimiter = delimiter
169
+
170
+ initialize_log(log)
171
+
172
+ Ken.logger = self
173
+
174
+ self.info("Logfile created") if log_creation
175
+ end
176
+
177
+ # Flush the entire buffer to the log object.
178
+ # Ken.logger.flush
179
+ #
180
+ def flush
181
+ return unless @buffer.size > 0
182
+ @log.write_method(@buffer.slice!(0..-1).join)
183
+ end
184
+
185
+ # Close and remove the current log object.
186
+ # Ken.logger.close
187
+ #
188
+ def close
189
+ flush
190
+ @log.close if @log.respond_to?(:close)
191
+ @log = nil
192
+ end
193
+
194
+ # Appends a string and log level to logger's buffer.
195
+
196
+ # @note
197
+ # Note that the string is discarded if the string's log level less than the
198
+ # logger's log level.
199
+ # @note
200
+ # Note that if the logger is aio capable then the logger will use
201
+ # non-blocking asynchronous writes.
202
+ #
203
+ # @param level<Fixnum> the logging level as an integer
204
+ # @param string<String> the message string to be logged
205
+ def push(string)
206
+ internal_push(string)
207
+ end
208
+ alias << push
209
+
210
+ # Generate the following logging methods for Ken.logger as described
211
+ # in the API:
212
+ # :fatal, :error, :warn, :info, :debug
213
+ # :off only gets a off? method
214
+ LEVELS.each_pair do |name, number|
215
+ unless name.to_s == 'off'
216
+ class_eval <<-EOS, __FILE__, __LINE__
217
+ # DOC
218
+ def #{name}(message)
219
+ self.<<( prep_msg(message, "#{name}") ) if #{name}?
220
+ end
221
+ EOS
222
+ end
223
+
224
+ class_eval <<-EOS, __FILE__, __LINE__
225
+ # DOC
226
+ def #{name}?
227
+ #{number} >= level
228
+ end
229
+ EOS
230
+ end
231
+
232
+ end # class Logger
233
+ end # module Ken
@@ -0,0 +1,91 @@
1
+ module Ken
2
+ class Property
3
+
4
+ include Extlib::Assertions
5
+
6
+ VALUE_TYPES = %w{
7
+ /type/id
8
+ /type/int
9
+ /type/float
10
+ /type/boolean
11
+ /type/text
12
+ /type/rawstring
13
+ /type/uri
14
+ /type/datetime
15
+ /type/key
16
+ }
17
+
18
+ # initializes a resource by json result
19
+ def initialize(data, type)
20
+ assert_kind_of 'data', data, Hash
21
+ assert_kind_of 'type', type, Ken::Type
22
+ @data, @type = data, type
23
+ end
24
+
25
+ # property id
26
+ # @api public
27
+ def id
28
+ @data["id"]
29
+ end
30
+
31
+ # property name
32
+ # @api public
33
+ def name
34
+ @data["name"]
35
+ end
36
+
37
+ # @api public
38
+ def to_s
39
+ name || id || ""
40
+ end
41
+
42
+ # @api public
43
+ def inspect
44
+ result = "#<Property id=\"#{id}\" expected_type=\"#{expected_type || "nil"}\" unique=\"#{unique?}\" object_type=\"#{object_type?}\">"
45
+ end
46
+
47
+ # returns the type of which the property is a part of
48
+ # every property always has exactly one type.
49
+ # that's why /type/property/schema is a unique property
50
+ # @api public
51
+ def type
52
+ @type
53
+ end
54
+
55
+ # reverse property, which represent incoming links
56
+ # @api public
57
+ def reverse_property
58
+ @data["reverse_property"]
59
+ end
60
+
61
+ # master property, which represent an outgoing link (or primitive value)
62
+ # @api public
63
+ def master_property
64
+ @data["master_property"]
65
+ end
66
+
67
+ # returns true if the property is unique
68
+ # @api public
69
+ def unique?
70
+ return @data["unique"]==true
71
+ end
72
+
73
+ # returns true if the property is an object type
74
+ # @api public
75
+ def object_type?
76
+ !value_type?
77
+ end
78
+
79
+ # returns true if the property is a value type
80
+ # @api public
81
+ def value_type?
82
+ VALUE_TYPES.include?(expected_type)
83
+ end
84
+
85
+ # type, which attribute values of that property are expected to have
86
+ # @api public
87
+ def expected_type
88
+ @data["expected_type"]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,144 @@
1
+ module Ken
2
+ class Resource
3
+ include Extlib::Assertions
4
+
5
+ attr_reader :data
6
+
7
+ # initializes a resource using a json result
8
+ def initialize(data)
9
+ assert_kind_of 'data', data, Hash
10
+ @schema_loaded, @attributes_loaded, @data = false, false, data
11
+ @data_fechted = data["/type/reflect/any_master"] != nil
12
+ end
13
+
14
+ # resource id
15
+ # @api public
16
+ def id
17
+ @data["id"] || ""
18
+ end
19
+
20
+ # resource guid
21
+ # @api public
22
+ def guid
23
+ @data['guid'] || ""
24
+ end
25
+
26
+ # resource name
27
+ # @api public
28
+ def name
29
+ @data["name"] || ""
30
+ end
31
+
32
+ # @api public
33
+ def to_s
34
+ name || id || ""
35
+ end
36
+
37
+ # @api public
38
+ def inspect
39
+ result = "#<Resource id=\"#{id}\" name=\"#{name || "nil"}\">"
40
+ end
41
+
42
+ # returns all assigned types
43
+ # @api public
44
+ def types
45
+ load_schema! unless schema_loaded?
46
+ @types
47
+ end
48
+
49
+ # returns all available views based on the assigned types
50
+ # @api public
51
+ def views
52
+ @views ||= Ken::Collection.new(types.map { |type| Ken::View.new(self, type) })
53
+ end
54
+
55
+ # returns individual view based on the requested type
56
+ # @api public
57
+ def view(type)
58
+ types.each { |t| return Ken::View.new(self, t) if t.id =~ /^#{Regexp.escape(type)}$/ }
59
+ nil
60
+ end
61
+
62
+ # returns all the properties from all assigned types
63
+ # @api public
64
+ def properties
65
+ @properties = Ken::Collection.new
66
+ types.each do |type|
67
+ @properties.concat(type.properties)
68
+ end
69
+ @properties
70
+ end
71
+
72
+ # returns all attributes for every type the resource is an instance of
73
+ # @api public
74
+ def attributes
75
+ load_attributes! unless attributes_loaded?
76
+ @attributes.values
77
+ end
78
+
79
+ # search for an attribute by name and return it
80
+ # @api public
81
+ def attribute(name)
82
+ attributes.each { |a| return a if a.property.id == name }
83
+ nil
84
+ end
85
+
86
+ # returns true if type information is already loaded
87
+ # @api public
88
+ def schema_loaded?
89
+ @schema_loaded
90
+ end
91
+
92
+ # returns true if attributes are already loaded
93
+ # @api public
94
+ def attributes_loaded?
95
+ @attributes_loaded
96
+ end
97
+ # returns true if json data is already loaded
98
+ # @api public
99
+ def data_fetched?
100
+ @data_fetched
101
+ end
102
+
103
+ private
104
+ # executes the fetch data query in order to load the full set of types, properties and attributes
105
+ # more info at http://lists.freebase.com/pipermail/developers/2007-December/001022.html
106
+ # @api private
107
+ def fetch_data
108
+ return @data if @data["/type/reflect/any_master"]
109
+ @data = Ken.session.mqlread(FETCH_DATA_QUERY.merge!(:id => id))
110
+ end
111
+
112
+ # loads the full set of attributes using reflection
113
+ # information is extracted from master, value and reverse attributes
114
+ # @api private
115
+ def load_attributes!
116
+ fetch_data unless data_fetched?
117
+ # master & value attributes
118
+ raw_attributes = Ken::Util.convert_hash(@data["/type/reflect/any_master"])
119
+ raw_attributes.merge!(Ken::Util.convert_hash(@data["/type/reflect/any_value"]))
120
+ @attributes = {}
121
+ raw_attributes.each_pair do |a, d|
122
+ properties.select { |p| p.id == a}.each do |p|
123
+ @attributes[p.id] = Ken::Attribute.create(d, p)
124
+ end
125
+ end
126
+ # reverse properties
127
+ raw_attributes = Ken::Util.convert_hash(@data["/type/reflect/any_reverse"])
128
+ raw_attributes.each_pair do |a, d|
129
+ properties.select { |p| p.master_property == a}.each do |p|
130
+ @attributes[p.id] = Ken::Attribute.create(d, p)
131
+ end
132
+ end
133
+ @attributes_loaded = true
134
+ end
135
+
136
+ # loads the resource's metainfo
137
+ # @api private
138
+ def load_schema!
139
+ fetch_data unless data_fetched?
140
+ @types = Ken::Collection.new(@data["ken:type"].map { |type| Ken::Type.new(type) })
141
+ @schema_loaded = true
142
+ end
143
+ end # class Resource
144
+ end # module Ken
@@ -0,0 +1,132 @@
1
+ module Ken
2
+ class << self #:nodoc:
3
+ attr_accessor :session
4
+ end
5
+
6
+ # A class for returing errors from the freebase api.
7
+ # For more infomation see the freebase documentation:
8
+ class ReadError < ArgumentError
9
+ attr_accessor :code, :msg
10
+ def initialize(code,msg)
11
+ self.code = code
12
+ self.msg = msg
13
+ end
14
+ def message
15
+ "#{code}: #{msg}"
16
+ end
17
+ end
18
+
19
+ class AttributeNotFound < StandardError; end
20
+ class PropertyNotFound < StandardError; end
21
+ class ResourceNotFound < StandardError; end
22
+ class ViewNotFound < StandardError; end
23
+
24
+ # partially taken from chris eppstein's freebase api
25
+ # http://github.com/chriseppstein/freebase/tree
26
+ class Session
27
+
28
+ public
29
+ # Initialize a new Ken Session
30
+ # Ken::Session.new(host{String, IO}, username{String}, password{String})
31
+ #
32
+ # @param host<String> the API host
33
+ # @param username<String> freebase username
34
+ # @param password<String> user password
35
+ def initialize(host, username, password)
36
+ @host = host
37
+ @username = username
38
+ @password = password
39
+
40
+ Ken.session = self
41
+
42
+ # TODO: check connection
43
+ Ken.logger.info("connection established.")
44
+ end
45
+
46
+ SERVICES = {
47
+ :mqlread => '/api/service/mqlread',
48
+ :mqlwrite => '/api/service/mqlwrite',
49
+ :login => '/api/account/login',
50
+ :upload => '/api/service/upload'
51
+ }
52
+
53
+ # get the service url for the specified service.
54
+ def service_url(svc)
55
+ "#{@host}#{SERVICES[svc]}"
56
+ end
57
+
58
+ SERVICES.each_key do |k|
59
+ define_method("#{k}_service_url") do
60
+ service_url(k)
61
+ end
62
+ end
63
+
64
+ # raise an error if the inner response envelope is encoded as an error
65
+ def handle_read_error(inner)
66
+ unless inner['code'][0, '/api/status/ok'.length] == '/api/status/ok'
67
+ Ken.logger.error "Read Error #{inner.inspect}"
68
+ error = inner['messages'][0]
69
+ raise ReadError.new(error['code'], error['message'])
70
+ end
71
+ end # handle_read_error
72
+
73
+
74
+ # perform a mqlread and return the results
75
+ # TODO: should support multiple queries
76
+ # you should be able to pass an array of queries
77
+ # Specify :cursor => true to batch the results of a query, sending multiple requests if necessary.
78
+ def mqlread(query, options = {})
79
+ Ken.logger.info ">>> Sending Query: #{query.to_json}"
80
+ cursor = options[:cursor]
81
+ if cursor
82
+ query_result = []
83
+ while cursor
84
+ response = get_query_response(query, cursor)
85
+ query_result += response['result']
86
+ cursor = response['cursor']
87
+ end
88
+ else
89
+ response = get_query_response(query, cursor)
90
+ cursor = response['cursor']
91
+ query_result = response['result']
92
+ end
93
+ query_result
94
+ end
95
+
96
+ protected
97
+ # returns parsed json response from freebase mqlread service
98
+ def get_query_response(query, cursor=nil)
99
+ envelope = { :qname => {:query => query }}
100
+ envelope[:qname][:cursor] = cursor if cursor
101
+
102
+ response = http_request mqlread_service_url, :queries => envelope.to_json
103
+
104
+ result = JSON.parse response
105
+
106
+ inner = result['qname']
107
+ handle_read_error(inner)
108
+ Ken.logger.info "<<< Received Response: #{inner['result'].inspect}"
109
+ inner
110
+ end
111
+
112
+ # encode parameters
113
+ def params_to_string(parameters)
114
+ parameters.keys.map {|k| "#{URI.encode(k.to_s)}=#{URI.encode(parameters[k])}" }.join('&')
115
+ end
116
+
117
+ # does the dirty work
118
+ def http_request(url, parameters = {})
119
+ params = params_to_string(parameters)
120
+ url << '?'+params unless params !~ /\S/
121
+
122
+ return Net::HTTP.get_response(::URI.parse(url)).body
123
+
124
+ fname = "#{MD5.md5(params)}.mql"
125
+ open(fname,"w") do |f|
126
+ f << response
127
+ end
128
+ Ken.logger.info("Wrote response to #{fname}")
129
+ end
130
+
131
+ end # class Session
132
+ end # module Ken
data/lib/ken/type.rb ADDED
@@ -0,0 +1,53 @@
1
+ module Ken
2
+ class Type
3
+
4
+ include Extlib::Assertions
5
+
6
+ # initializes a resource using a json result
7
+ def initialize(data)
8
+ assert_kind_of 'data', data, Hash
9
+ @data = data
10
+ end
11
+
12
+ # access property info
13
+ # @api public
14
+ def properties
15
+ @properties ||= Ken::Collection.new(@data["properties"].map { |property| Ken::Property.new(property, self) })
16
+ end
17
+
18
+ # type id
19
+ # @api public
20
+ def id
21
+ @data["id"]
22
+ end
23
+
24
+ # type name
25
+ # @api public
26
+ def name
27
+ @data["name"]
28
+ end
29
+
30
+ # @api public
31
+ def to_s
32
+ name || id || ""
33
+ end
34
+
35
+ # @api public
36
+ def inspect
37
+ result = "#<Type id=\"#{id}\" name=\"#{name || "nil"}\">"
38
+ end
39
+
40
+ # delegate to property_get
41
+ def method_missing sym
42
+ property_get(sym.to_s)
43
+ end
44
+
45
+ private
46
+ # @api private
47
+ # search for a property by name and return it
48
+ def property_get(name)
49
+ properties.each { |p| return p if p.id =~ /\/#{name}$/ }
50
+ raise PropertyNotFound
51
+ end
52
+ end
53
+ end
data/lib/ken/util.rb ADDED
@@ -0,0 +1,18 @@
1
+ module Ken
2
+ module Util
3
+ # magic hash conversion
4
+ def convert_hash(source)
5
+ source.inject({}) do |result, item|
6
+ if result[item["link"]]
7
+ result[item["link"]] << { "id" => item["id"], "name" => item["name"], "value" => item["value"] }
8
+ else
9
+ result[item["link"]] = []
10
+ result[item["link"]] << { "id" => item["id"], "name" => item["name"], "value" => item["value"] }
11
+ end
12
+ result
13
+ end
14
+ end
15
+ module_function :convert_hash
16
+ end
17
+ end
18
+