adamsalter-ken 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
+