Skip to content

Commit

Permalink
Add initial documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
pusewicz committed May 3, 2010
1 parent 78a6c7a commit 42a54ac
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ tmp/**/*
.DS_Store
*~
example/config.yml
.yardoc/
doc/
7 changes: 4 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ end
#gem "cucumber"
#end

#group :development do
group :development do
gem "yard"
#gem "ruby_core_source"
#gem "jeweler"
gem "jeweler"
#gem "ruby-debug19"
#end
end
31 changes: 18 additions & 13 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,26 @@ rescue LoadError
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
end

require 'spec/rake/spectask'
Spec::Rake::SpecTask.new(:spec) do |spec|
spec.libs << 'lib' << 'spec'
spec.spec_files = FileList['spec/**/*_spec.rb']
end
begin
require 'spec/rake/spectask'
Spec::Rake::SpecTask.new(:spec) do |spec|
spec.libs << 'lib' << 'spec'
spec.spec_files = FileList['spec/**/*_spec.rb']
end

Spec::Rake::SpecTask.new(:rcov) do |spec|
spec.libs << 'lib' << 'spec'
spec.pattern = 'spec/**/*_spec.rb'
spec.rcov = true
end
Spec::Rake::SpecTask.new(:rcov) do |spec|
spec.libs << 'lib' << 'spec'
spec.pattern = 'spec/**/*_spec.rb'
spec.rcov = true
end

task :spec => :check_dependencies
task :spec => :check_dependencies
task :default => :spec
rescue LoadError
task :spec do
abort "RSpec is not available. In order to run rspec, you must: sudo gem install rspec"
end
end

begin
require 'cucumber/rake/task'
Expand All @@ -55,8 +62,6 @@ rescue LoadError
end
end

task :default => :spec

begin
require 'yard'
YARD::Rake::YardocTask.new
Expand Down
7 changes: 7 additions & 0 deletions lib/sonia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'logger'
require 'active_support'

# @author Piotr Usewicz
module Sonia
autoload :Server, 'sonia/server'
autoload :Widget, 'sonia/widget'
Expand All @@ -11,10 +12,16 @@ module Sonia
autoload :Config, 'sonia/config'
autoload :Helpers, 'sonia/helpers'

# Returns application logger
#
# @return [Logger]
def self.log
@logger ||= Logger.new(STDOUT)
end

# Returns expanded path to the root directory
#
# @return [String] expanded path to the root directory
def self.root
@@root ||= File.expand_path(File.join(File.dirname(__FILE__), '..'))
end
Expand Down
26 changes: 26 additions & 0 deletions lib/sonia/config.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
module Sonia
# Simple configuration class
#
# @example
# "Config.new({:name => "Piotr"}).piotr" #=> "Piotr"
class Config
# @param [Hash] data Hash containing configuration data
def initialize(data={})
@data = {}
update!(data)
end

# Updates configuration data
#
# @param [Hash] data
def update!(data)
data.each do |key, value|
self[key] = value
end
end

# Returns configuration value
#
# @param [#to_sym] key Key of the value
def [](key)
@data[key.to_sym]
end

# Allows setting a value
#
# @param [#to_sym] key Key of the value
# @param [Object] value Configuration value
def []=(key, value)
if value.class == Hash
@data[key.to_sym] = Config.new(value)
Expand All @@ -23,6 +38,10 @@ def []=(key, value)
end
end

# Returns each configuration key - value pair or an iterator
#
# @yield [key, value]
# @param [Enumerator]
def each
if block_given?
@data.each do |k, v|
Expand All @@ -33,6 +52,10 @@ def each
end
end

# Returns configuration value by missing method name
#
# @param [Symbol] sym Key name as symbol
# @return [Object]
def method_missing(sym, *args)
if sym.to_s =~ /(.+)=$/
self[$1] = args.first
Expand All @@ -41,6 +64,9 @@ def method_missing(sym, *args)
end
end

# Returns whole configuration data as hash
#
# @return [Hash]
def to_hash
@data
end
Expand Down
6 changes: 6 additions & 0 deletions lib/sonia/helpers.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
module Sonia
module Helpers
# Returns all available widgets with relative path recognized by the webserver
#
# @return [Array] Array of relalive widget Javascript paths
def widget_javascripts
Dir[Sonia.root + "/widgets/*/*.js"].map do |file|
widget_name = File.basename(file, ".js")
file.gsub(File.join(Sonia.root, "widgets"), "/javascripts")
end
end

# Returns all available widget stylesheets with relative paths recognized by the webserver
#
# @return [Array] Array of relative widget CSS files
def widget_stylesheets
Dir[Sonia.root + "/widgets/*/*.css"].map do |file|
widget_name = File.basename(file, ".css")
Expand Down
29 changes: 28 additions & 1 deletion lib/sonia/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ def send(data)
end

module Sonia
# Main Sonia event loop
#
# Starts both WebServer and WebSocket server
#
# @author Piotr Usewicz
module Server
extend self

Expand All @@ -27,24 +32,38 @@ module Server
WEBSERVER_HOST = "localhost"
WEBSERVER_PORT = 3000

# Starts the server
#
# @param [Hash] args Startup options
def run!(args, &block)
@start_block = block
configure(args)
serve
end

# Returns configuration from the config file
#
# @return [Config]
def config
@@config
end

# Loads the configuration file
#
# @param [Hash] options
# @return [Config]
def configure(options)
@@config = Config.new(YAML.load_file(File.expand_path(options.config)))
end

# Returns [Logger] object
#
# @return [Logger]
def log
Sonia.log
end

# Starts main [EventMachine] loop
def serve
EventMachine.run {
initialize_widgets
Expand All @@ -54,6 +73,7 @@ def serve
}
end

# Goes through configured widgets and initializes them
def initialize_widgets
@widgets = []

Expand All @@ -64,11 +84,14 @@ def initialize_widgets
end
end

# Returns websocket configuration options
#
# @return [Hash] Websocket's host and port number
def websocket_options
{ :host => WEBSOCKET_HOST, :port => WEBSOCKET_PORT }
end


# Starts WebSocket server
def start_web_socket_server
EventMachine::WebSocket.start(websocket_options) do |ws|
ws.onopen {
Expand Down Expand Up @@ -101,10 +124,14 @@ def start_web_socket_server
log.info("Server") { "WebSocket Server running at #{websocket_options[:host]}:#{websocket_options[:port]}" }
end

# Starts Thin WebServer
def start_web_server
Thin::Server.start(WEBSERVER_HOST, WEBSERVER_PORT, ::Sonia::WebServer)
end

# Returns WebServer URL
#
# @return [String] WebServer URL
def webserver_url
"http://#{WEBSERVER_HOST}:#{WEBSERVER_PORT}"
end
Expand Down
5 changes: 5 additions & 0 deletions lib/sonia/web_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
require 'haml'

module Sonia
# Sonia's Web Server
#
# Allows dynamically generating HTML pages
#
# @author Piotr Usewicz
class WebServer < Sinatra::Base
helpers Sonia::Helpers

Expand Down
36 changes: 36 additions & 0 deletions lib/sonia/widget.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "digest/sha1"

module Sonia
# @abstract
class Widget
class << self
def inherited(subclass)
Expand All @@ -17,6 +18,9 @@ def widgets

attr_reader :widget_id, :channel, :sid, :config, :log

# Initalizes the widget
#
# @param [Hash] config Configuration of the widget from the config file
def initialize(config)
@log = Sonia.log
@channel = EM::Channel.new
Expand All @@ -28,41 +32,67 @@ def initialize(config)
].join)
end

# Returns JSON encode
#
# @return [Yajl::Encoder]
def encoder
@encoder ||= Yajl::Encoder.new
end

# Returns JSON parser
#
# @return [Yajl::Parser]
def parser
@decoder ||= Yajl::Parser.new
end

# Parses JSON
#
# @param [String] payload JSON string
# @return [Hash] Parsed JSON represented as a hash
def parse_json(payload)
Yajl::Parser.parse(payload)
end

# Parses YAML
#
# @param [String] payload YAML string
# @return [Hash] Parsed YAML represented as a hash
def parse_yaml(payload)
YAML.load(payload)
end

# Parse XML
#
# @param [String] payload XML string
# @return [Nokogiri::XML::Document] Parsed Nokogiri document
def parse_xml(payload)
Nokogiri(payload)
end

# Used to push initial data after setup
#def initial_push; end

# Subscribes a websocket to widget's data channel
#
# @param [EventMachine::WebSocket] websocket
# @return [String] Subscriber ID
def subscribe!(websocket)
@sid = channel.subscribe { |msg| websocket.send msg }
ensure
log.info(widget_name) { "Subscribed #{sid} via #{channel}" }
end

# Unsubscribes a subscriber id from data channel
def unsubscribe!
channel.unsubscribe(sid)
ensure
log.info(widget_name) { "Unsubscribed #{sid} via #{channel}" }
end

# Pushes data to the channel
#
# @param [Hash] msg Data which can be JSONified
def push(msg)
payload = {
:payload => msg,
Expand All @@ -77,10 +107,16 @@ def push(msg)
log.info(widget_name) { "Pushing #{message.inspect} via #{channel}" }
end

# Returns widget name
#
# @return [String] Name of the widget
def widget_name
self.class.name.split("::").last
end

# Initial widget setup data that gets pushed to the client
#
# @return [Hash] Initial widget information and configuration
def setup
{
:widget => self.widget_name,
Expand Down
1 change: 1 addition & 0 deletions lib/sonia/widgets.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'active_support/inflector'

module Sonia
# Responsible for just namespacing widgets
module Widgets
Dir[File.join(Sonia.root, "/widgets/*/*.rb")].each do |file|
autoload File.basename(file, '.rb').classify.to_sym, file
Expand Down

0 comments on commit 42a54ac

Please sign in to comment.