picombo 0.1.0

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/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