picombo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bootstrap.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'thin'
2
+ require 'erb'
3
+ require 'yaml'
4
+ require 'dm-core'
5
+ require 'singleton'
6
+ require 'core/core'
7
+ require 'classes/config'
8
+
9
+ def run_system()
10
+ app = Rack::Builder.new do
11
+ use Rack::ShowExceptions
12
+ #use Rack::Reloader
13
+ use Rack::Static, :urls => ['/css', '/images']
14
+ use Rack::Session::Cookie
15
+ run Picombo::Core.new
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ module Picombo
2
+ # == Benchmarking Class
3
+ #
4
+ # The benchmark class allows you to run simple named benchmarks.
5
+ #
6
+ # === Usage
7
+ #
8
+ # To start a benchmark, use the Picombo::Bench.instance.start(name) method. Name should be unique to the benchmark being ran.
9
+ # # Start a simple benchmark
10
+ # Picombo::Bench.instance.start(:myapp_simple_mark)
11
+ #
12
+ # To stop a benchmark, use the Picombo::Bench.instance.stop(name) method.
13
+ # # Stop the previous benchmark
14
+ # Picombo::Bench.instance.stop(:myapp_simple_mark)
15
+ #
16
+ # You can retrieve the benchmark results anytime after you stop the benchmark with Picombo::Bench.instance.get(name)
17
+ # # Retrieve the previously ran benchmark
18
+ # time = Picombo::Bench.instance.get(:myapp_simple_mark)
19
+ #
20
+ # You can alter the precision of the returned benchmark time with the second parameter, it defaults to four decimal places
21
+ # # Only go to two decimal places
22
+ # time = Picombo::Bench.instance.get(:myapp_simple_mark, 2)
23
+ #
24
+ # get() will return nil if the named benchmark doesn't exist
25
+ class Bench
26
+ include Singleton
27
+
28
+ # Internal hash of benchmarks
29
+ @@marks = {}
30
+
31
+ # Starts a benchmark defined by name
32
+ def start(name)
33
+ @@marks[name] = {'start' => Time.new,
34
+ 'stop' => nil}
35
+ end
36
+
37
+ # Stops a benchmark defined by name
38
+ def stop(name)
39
+ # only stop if the benchmark exists
40
+ if (@@marks.has_key?(name))
41
+ @@marks[name]['stop'] = Time.new
42
+ end
43
+ end
44
+
45
+ # Gets a benchmark result defined by name
46
+ def get(name, precision = 4)
47
+ if ( ! @@marks.has_key?(name))
48
+ return nil
49
+ else
50
+ start = @@marks[name]['start'].to_f
51
+ stop = @@marks[name]['stop'].to_f
52
+ rounded_number = (Float((stop-start)) * (10 ** precision)).round.to_f / 10 ** precision
53
+ return rounded_number.to_s
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,89 @@
1
+ module Picombo
2
+ # == Cache Class
3
+ #
4
+ # The cache class allows you to serialize ruby objects,
5
+ # store them in a backend system, and retreive them later.
6
+ #
7
+ # Caching can be useful for methods that take a long time to run,
8
+ # or complex database queries that cannot be made quicker.
9
+ #
10
+ # === Usage
11
+ #
12
+ # To set a cache item, use the set method:
13
+ # Picombo::Cache.new.set(:test, '<h1>It Works!</h1><p>This is a test.</p>')
14
+ # You can also pass a hash into the first parameter to set multiple items
15
+ #
16
+ # To retreive a cache item, use the get method:
17
+ # html = Picombo::Cache.new.get(:test)
18
+ # # html will contain '<h1>It Works!</h1><p>This is a test.</p>'
19
+ #
20
+ # To delete an existing cache item, use the delete method
21
+ # Picombo::Cache.new.delete(:test)
22
+ #
23
+ # === Drivers
24
+ #
25
+ # The cache class supports drivers, which will allow you to use different backend
26
+ # systems to store your cache items. Right now only a file driver is available.
27
+ # You can write your own cache drivers to behave how you want as well.
28
+ #
29
+ # By default the cache driver will use the "default" group in your config file: cache.yaml
30
+ # You can specify multiple groups as well.
31
+ #
32
+ # ==== Usage
33
+ #
34
+ # To call the cache driver with an alternate group, just pass that group name into the new method
35
+ # Picombo::Cache.new('my_other_group')
36
+ #
37
+ # To do this, you need to define the 'my_other_group' in your config file
38
+ #
39
+ # === Lifetimes
40
+ #
41
+ # The cache class supports lifetimes, which will automatically expire cache items after the
42
+ # specified time period. These are specifies in seconds from the current time. When you use the
43
+ # set method, you can set a specific lifetime with the third parameter.
44
+ # The default is located in the config file group
45
+ class Cache
46
+
47
+ @@driver = nil
48
+ @@config = nil
49
+ @@group = nil
50
+
51
+ # Set up the driver
52
+ def initialize(group = nil)
53
+ group = 'default' if group == nil
54
+ @@group = group
55
+ @@config = Picombo::Config.get('cache.'+group)
56
+ driver_name = 'Cache_'+@@config['driver'].capitalize!
57
+ @@driver = Picombo::const_get(driver_name).new(@@config)
58
+ end
59
+
60
+ # Sets a cache item
61
+ def set(key, value = nil, lifetime = nil)
62
+ lifetime = Picombo::Config.get('cache.'+@@group+'.lifetime').to_i if lifetime == nil
63
+ unless key.is_a? Hash
64
+ key = {key => value}
65
+ end
66
+
67
+ @@driver.set(key, lifetime)
68
+ end
69
+
70
+ # Retreives a cache item
71
+ def get(key)
72
+ @@driver.get(key.to_s, (key.is_a? Array) ? true : false)
73
+ end
74
+
75
+ # Deletes a cache item
76
+ def delete(keys)
77
+ unless key.is_a? Array
78
+ key = [key]
79
+ end
80
+
81
+ @@driver.delete(keys)
82
+ end
83
+
84
+ # Deletes all the cache items
85
+ def delete_all
86
+ @@driver.delete_all
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,80 @@
1
+ module Picombo
2
+ class Cache_File
3
+ @@config = nil
4
+
5
+ def initialize(config)
6
+ @@config = config
7
+ raise "Cache directory does not exist: "+APPPATH+'cache' unless File.directory?(APPPATH+'cache/')
8
+ raise "Cache directory not writable: "+APPPATH+'cache' unless File.writable?(APPPATH+'cache/')
9
+ end
10
+
11
+ def exists(keys)
12
+ return Dir.glob(APPPATH+'cache/*~*') if keys == true
13
+
14
+ paths = []
15
+ [*keys].each do |key|
16
+ Dir.glob(APPPATH+'cache/'+key.to_s+'~*').each do |path|
17
+ paths.push(path)
18
+ end
19
+ end
20
+
21
+ paths.uniq
22
+ end
23
+
24
+ def set(items, lifetime)
25
+ lifetime+=Time.now.to_i
26
+
27
+ success = true
28
+
29
+ items.each do |key, value|
30
+ delete(key)
31
+
32
+ File.open(APPPATH+'cache/'+key.to_s+'~'+lifetime.to_s, 'w') {|f| f.write(Marshal.dump(value)) }
33
+ end
34
+
35
+ return true
36
+ end
37
+
38
+ def get(keys, single = false)
39
+ items = []
40
+
41
+ exists(keys).each do |file|
42
+ if expired(file)
43
+ next
44
+ end
45
+
46
+ contents = ''
47
+ File.open(file, "r") do |infile|
48
+ while (line = infile.gets)
49
+ contents+=line+"\n"
50
+ end
51
+ end
52
+ items.push(Marshal.load(contents))
53
+ end
54
+ items.uniq!
55
+
56
+ if single
57
+ return items.length > 0 ? items.shift : nil
58
+ else
59
+ return items
60
+ end
61
+ end
62
+
63
+ def delete(keys)
64
+ exists(keys).each do |file|
65
+ File.unlink(file)
66
+ end
67
+ end
68
+
69
+ def delete_all
70
+ exists(true).each do |file|
71
+ File.unlink(file)
72
+ end
73
+ end
74
+
75
+ def expired(file)
76
+ expires = file[file.index('~') + 1, file.length]
77
+ return (expires != 0 and expires.to_i <= Time.now.to_i);
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,81 @@
1
+ module Picombo
2
+ class Config
3
+ include Enumerable
4
+
5
+ # Internal cache.
6
+ @@cache = {}
7
+
8
+ # Loads configuration files by name
9
+ def self.load(name)
10
+ # Look for this config item in the cache
11
+ if @@cache.has_key?(name)
12
+ return @@cache[name]
13
+ end
14
+
15
+ configuration, files = {}, Picombo::Core.find_file('config', name, false, 'yaml')
16
+
17
+ files.each do |file|
18
+ configuration.merge! YAML::load_file(file)
19
+ end
20
+
21
+ # Set the internal cache
22
+ @@cache[name] = configuration
23
+
24
+ configuration
25
+ rescue Errno::ENOENT => e
26
+ # A config file couldn't be loaded...
27
+ end
28
+
29
+ # Used for dynamic config setting. Not implimented yet
30
+ def self.set(key, value)
31
+ end
32
+
33
+ # Retrieves a config item in dot notation
34
+ def self.get(key, required = true)
35
+ # get the group name from the key
36
+ key = key.split('.')
37
+ group = key.shift
38
+
39
+ value = key_string(load(group), key.join('.'))
40
+
41
+ raise "Config key '"+key.join('.')+"' not found!!" if required && value.nil?
42
+
43
+ value
44
+ end
45
+
46
+ # Clears the internal cache for a config file(s)
47
+ def self.clear(group)
48
+ @@cache.delete(group)
49
+ end
50
+
51
+ # Allows searching an hash by dot seperated strings
52
+ def self.key_string(hash, keys)
53
+ return false if ! hash.is_a? Hash
54
+
55
+ keys = keys.split('.')
56
+
57
+ until keys.length == 0
58
+ key = keys.shift
59
+
60
+ if hash.has_key? key
61
+ if hash[key].is_a? Hash and ! keys.empty?
62
+ hash = hash[key]
63
+ else
64
+ return hash[key]
65
+ end
66
+ else
67
+ break
68
+ end
69
+ end
70
+
71
+ nil
72
+ end
73
+
74
+ #
75
+ # Define an each method, required for Enumerable.
76
+ #
77
+ def self.each(file)
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,27 @@
1
+ module Picombo
2
+ class Cookie
3
+ include Singleton
4
+
5
+ # Initializes the request for cookies
6
+ def init(req)
7
+ @@req = req
8
+ end
9
+
10
+ # Retrieves a cookie item defined by key
11
+ def self.get(key, default = nil)
12
+ return @@req.cookies[key] if @@req.cookies.has_key?(key)
13
+
14
+ default
15
+ end
16
+
17
+ # Sets a cookie item defined by key to val
18
+ def self.set(key, val)
19
+ Picombo::Core.raw_response.set_cookie(key, val)
20
+ end
21
+
22
+ # Deletes a cookie item defined by key
23
+ def self.delete(key)
24
+ Picombo::Core.raw_response.delete_cookie(key)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module Picombo
2
+ class E404 < Exception
3
+
4
+ end
5
+ end
@@ -0,0 +1,136 @@
1
+ module Picombo
2
+ # == Event Class
3
+ #
4
+ # Process queuing/execution class. Allows an unlimited number of callbacks
5
+ # to be added to 'events'. Events can be run multiple times, and can also
6
+ # process event-specific data. By default, Picombo has several system events.
7
+ #
8
+ # The event class is a powerful system where you can add, modify or remove Picombo functionality, and you can also create your own events to leverage easy plugablity features in your applicetion.
9
+ #
10
+ # === Examples
11
+ # It's very easy to use events to alter system behavior. This event addition will merge some parameters to every controller call:
12
+ # Picombo::Event.add('system.post_router') do |data|
13
+ # data.merge!({:params => ['test', 'test', 'test']})
14
+ # end
15
+ # This event will be called on the system.post_router event, after the router has parsed the URI
16
+ #
17
+ # Because the system.post_router is called as Picombo::Event.run('system.post_router', uri) it passes the routed uri variable as data in the method above.
18
+ #
19
+ # You can also add class methods to events:
20
+ # Picombo::Event.add('system.shutdown', 'Picombo::Foobar.new.write_access_log')
21
+ # This might process some data and then write it to an event log on the system.shutdown event right before the output is sent to the browser
22
+
23
+ class Event
24
+ private
25
+ # Events that have ran.
26
+ @@ran = []
27
+
28
+ # Event-specific data.
29
+ @@data = nil
30
+
31
+ # Callbacks.
32
+ @@events = {}
33
+
34
+ public
35
+ # Data reader.
36
+ def self.data()
37
+ @@data
38
+ end
39
+
40
+ # Data writer.
41
+ def self.data=(data)
42
+ @@data = data
43
+ end
44
+
45
+ #
46
+ # Add a callback to an event queue.
47
+ #
48
+ def self.add(name, callback = nil, &block)
49
+ # Create the event queue if it doesn't exist yet.
50
+ unless @@events.include?(name) and ! @@events.empty?
51
+ @@events[name] = []
52
+ end
53
+
54
+ # Make sure there isn't a callback and a block, two callbacks at the same time doesn't make sense.
55
+ raise ArgumentError.new('You cannot add a callback to an event queue and specify a block, at the same time.') if ! callback.nil? and block_given?
56
+
57
+ if block_given?
58
+ @@events[name].push(block)
59
+ else
60
+ # If the callback is a string/array, make sure it doesn't already exist.
61
+ if ! callback.respond_to?(:call)
62
+ return false if @@events[name].include?(callback)
63
+ end
64
+
65
+ @@events[name].push(callback)
66
+ end
67
+ end
68
+
69
+ #
70
+ # Return all of the callbacks attached to an event, or an empty array if there are none.
71
+ #
72
+ def self.get(name)
73
+ ( ! @@events.include?(name) or @@events[name].empty?) ? [] : @@events[name]
74
+ end
75
+
76
+ #
77
+ # Remove one or all of the callbacks from a given event.
78
+ #
79
+ def self.clear!(name, callback = nil)
80
+ return false unless @@events.include?(name)
81
+
82
+ if callback.nil?
83
+ @@events[name] = []
84
+ else
85
+ @@events[name].delete(callback)
86
+ end
87
+ end
88
+
89
+ #
90
+ # Execute all of the callbacks attached to a given event.
91
+ #
92
+ def self.run(name, data = nil)
93
+ # Make sure the event queue exists and has at least one attached callback.
94
+ return false unless @@events.include?(name) and ! @@events[name].empty?
95
+
96
+ @@data = data
97
+
98
+ @@events[name].each do |callback|
99
+ if callback.is_a?(Array)
100
+ if callback[0].is_a?(String)
101
+ const_get(callback[0]).send(callback[1])
102
+ else
103
+ callback[0].send(callback[1])
104
+ end
105
+ elsif callback.is_a?(String)
106
+ eval(callback)
107
+ #callback = callback.split('.')
108
+
109
+ #if callback.length > 1
110
+ # namespace = Object
111
+ # callback[0].split("::").each do |const|
112
+ # namespace = namespace.const_get(const)
113
+ # end
114
+
115
+ # Picombo::Log.write('info', 'trying to run '+namespace.inspect+'.'+callback[1])
116
+ # namespace.send(callback[1])
117
+ #else
118
+ # Kernel.send(callback[0])
119
+ #end
120
+ elsif callback.is_a?(Proc)
121
+ callback.call(data)
122
+ end
123
+ end
124
+
125
+ @@ran.push(name)
126
+ @@data = nil
127
+ end
128
+
129
+ #
130
+ # Determine whether or not an event has ran yet.
131
+ #
132
+ def self.ran?(name)
133
+ @@ran.include?(name)
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,45 @@
1
+ module Picombo
2
+
3
+ # Helper class to generate HTML
4
+
5
+ class Html
6
+ include Singleton
7
+
8
+ # Returns a safe email address, by encoding each character in the email
9
+ def self.email(email)
10
+ safe = []
11
+
12
+ email.each_byte do |char|
13
+ safe << '&#'+char.to_s
14
+ end
15
+
16
+ safe.join
17
+ end
18
+
19
+ def self.style(css)
20
+ unless css.is_a? Array
21
+ return '<link type="text/css" href="'+Picombo::Url.base()+css+'" rel="stylesheet" />'
22
+ end
23
+
24
+ to_return = ''
25
+ css.each do |file|
26
+ to_return+='<link type="text/css" href="'+Picombo::Url.base()+file+'" rel="stylesheet" />'+"\n"
27
+ end
28
+
29
+ to_return
30
+ end
31
+
32
+ def self.script(js)
33
+ unless js.is_a? Array
34
+ return '<script type="text/javascript" src="'+Url.base()+js+'"></script>'
35
+ end
36
+
37
+ to_return = ''
38
+ js.each do |file|
39
+ to_return+='<script type="text/javascript" src="'+Url.base()+file+'"></script>'+"\n"
40
+ end
41
+
42
+ to_return
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,59 @@
1
+ module Picombo
2
+ # Provides access and XSS filtering to GET and POST input variables
3
+ #
4
+ # == Using Input
5
+ # * To fetch get variables, simple use Picombo::Input.instance().get(get_key)
6
+ # * To fetch post variables, simple use Picombo::Input.instance().post(post)
7
+ #
8
+ # * To return the entire input hash, omit the key
9
+ # * If the key is not found, the default parameter is returned, by default nil
10
+ #
11
+ # === XSS Filtering
12
+ #
13
+ # in the main config file, there is an xss_clean option. If you set this to true,
14
+ # all GET and POST variables will be scanned and cleaned of script data. You can manually
15
+ # filter strings by using:
16
+ # Picombo::Input.instance.xss_clean(str)
17
+ class Input
18
+ include Singleton
19
+
20
+ # Sets the input request
21
+ def set_request(req)
22
+ @req = req
23
+
24
+ if Picombo::Config.get('config.xss_clean')
25
+ @req.GET().each do |key, value|
26
+ Picombo::Log.write(:debug, 'Cleaning GET key: '+key)
27
+ @req.GET()[key] = Picombo::Security.xss_clean(value, Picombo::Config.get('config.xss_clean'))
28
+ end
29
+ @req.POST().each do |key, value|
30
+ Picombo::Log.write(:debug, 'Cleaning POST key: '+key)
31
+ @req.POST()[key] = Picombo::Security.xss_clean(value, Picombo::Config.get('config.xss_clean'))
32
+ end
33
+ end
34
+ Picombo::Log.write(:debug, 'Input Library initialized')
35
+ end
36
+
37
+ # Retrieves a GET item by key. If the key doesn't exist, return default
38
+ # Optionaly returns the entire GET hash if key is nil
39
+ def get(key = nil, default = nil)
40
+ return @req.GET() if key.nil?
41
+
42
+ get = @req.GET()
43
+ return get[key] if get.has_key?(key)
44
+
45
+ default
46
+ end
47
+
48
+ # Retrieves a POST item by key. If the key doesn't exist, return default
49
+ # Optionaly returns the entire POST hash if key is nil
50
+ def post(key = nil, default = nil)
51
+ return @req.POST() if key.nil?
52
+
53
+ post = @req.POST()
54
+ return post[key] if post.has_key?(key)
55
+
56
+ default
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ module Picombo
2
+ # Provides file logging capability
3
+ #
4
+ # == Log Levels
5
+ # Picombo has the following log levels
6
+ # * 0 - Disable logging (none)
7
+ # * 1 - Errors and exceptions (info)
8
+ # * 2 - Warnings (warning)
9
+ # * 3 - Notices (notice)
10
+ # * 4 - Debugging (debug)
11
+ #
12
+ # The log level is set in config/log.yaml
13
+ #
14
+ # To write a log entry, do Picombo::Log.instance().write(:info, 'This is the message!')
15
+ #
16
+ class Log
17
+ include Singleton
18
+
19
+ # Writes log entry with level +type+:: with +message+::
20
+ def self.write(type, message)
21
+ types = {:none => 0, :info => 1, :warning => 2, :notice => 3, :debug => 4}
22
+
23
+ if types[type] <= Picombo::Config.get('log.log_threshold')
24
+ t = Time.now
25
+ f = File.new(APPPATH+Picombo::Config.get('log.directory')+t.year.to_s+'-'+t.month.to_s+'-'+t.day.to_s+'.log', 'a')
26
+ f.write(t.to_s+' --- '+type.to_s+': '+message.to_s+"\n");
27
+ f.close
28
+ end
29
+ end
30
+ end
31
+ end