rayo 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.
@@ -0,0 +1,11 @@
1
+ module Rayo
2
+
3
+ module Tags
4
+ end
5
+
6
+ module Models
7
+ end
8
+
9
+ end
10
+
11
+ require File.join(File.dirname(__FILE__), 'rayo', 'application.rb')
@@ -0,0 +1,54 @@
1
+ require 'sinatra/base'
2
+
3
+ require File.join(File.dirname(__FILE__), 'storage.rb')
4
+ require File.join(File.dirname(__FILE__), 'config.rb')
5
+
6
+ class Rayo::Application < Sinatra::Base
7
+
8
+ class << self
9
+
10
+ attr_accessor :config # Application configuration object
11
+
12
+ # Configure application
13
+ def configure
14
+ yield @config ||= Rayo::Config.new
15
+ end
16
+
17
+ end
18
+
19
+ def config
20
+ self.class.config
21
+ end
22
+
23
+ get '/' do
24
+ redirect_to_lang '' # Root page
25
+ end
26
+
27
+ get '*/' do |path|
28
+ redirect path # Remove trailing slashes
29
+ end
30
+
31
+ get '/*' do |path|
32
+ path = path.split '/' # Split path into segments
33
+
34
+ return redirect_to_lang path unless config.languages.include? path.first
35
+
36
+ lang = path.shift # Determine language
37
+ storage = Rayo::Storage.new( config, lang ) # Page storage
38
+ page = storage.page( path ) # Find page by path
39
+ page = storage.status_page( path, 404 ) unless page && page.file # Render 404 page if there are no page, or there are no file
40
+
41
+ [ page[:status], page.render ] # Return page status and content
42
+ end
43
+
44
+ private
45
+
46
+ def select_language
47
+ config.languages.first
48
+ end
49
+
50
+ def redirect_to_lang( path )
51
+ redirect [select_language, *path].join '/'
52
+ end
53
+
54
+ end
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__), 'taggable.rb')
2
+ require File.join(File.dirname(__FILE__), 'tags/property_tags.rb')
3
+ require File.join(File.dirname(__FILE__), 'tags/content_tags.rb')
4
+ require File.join(File.dirname(__FILE__), 'tags/navigation_tags.rb')
5
+
6
+ class Rayo::Config
7
+
8
+ attr_accessor :content_dir
9
+ attr_accessor :languages
10
+ attr_accessor :page_exts
11
+
12
+ def initialize
13
+ @languages = ['en']
14
+ @page_exts = ['.yml']
15
+
16
+ @filters = {}
17
+
18
+ # Default filters
19
+ add_filter('html'){|source| source }
20
+
21
+ @tagger = Object.new
22
+ @tagger.extend Rayo::Taggable
23
+
24
+ # Default tags
25
+ add_tags Rayo::Tags::PropertyTags
26
+ add_tags Rayo::Tags::ContentTags
27
+ add_tags Rayo::Tags::NavigationTags
28
+ end
29
+
30
+ # Add tags defined in module
31
+ def add_tags( tag_module )
32
+ @tagger.extend tag_module
33
+ end
34
+
35
+ # Add filter
36
+ def add_filter( ext, &filter )
37
+ @filters[".#{ext}"] = filter
38
+ end
39
+
40
+ # Create new object containing all defined tags
41
+ def create_tagger
42
+ @tagger.clone
43
+ end
44
+
45
+ def directory( content_type )
46
+ File.join( @content_dir, content_type.to_s )
47
+ end
48
+
49
+ def renderable_exts
50
+ @filters.keys
51
+ end
52
+
53
+ def filter( ext )
54
+ @filters[ ext.to_s ]
55
+ end
56
+
57
+ end
@@ -0,0 +1,94 @@
1
+ require 'yaml'
2
+ require 'erubis'
3
+ require 'radius'
4
+
5
+ require File.join(File.dirname(__FILE__), '..', 'tag_context.rb')
6
+
7
+ class Rayo::Models::Page
8
+
9
+ attr_reader :storage, :parent, :path
10
+
11
+ def initialize( storage, parent, path )
12
+ @storage = storage
13
+ @parent = parent
14
+ @path = path
15
+ end
16
+
17
+ def descendant( relative_path )
18
+ relative_path.inject( self ) {|page,slug| page.child( slug ) }
19
+ end
20
+
21
+ def children
22
+ @children ||= @storage.find_pages( directories ).map{|name| child( name ) }
23
+ end
24
+
25
+ def child( slug )
26
+ @children_cache ||= {}
27
+ return @children_cache[ slug ] if @children_cache.include? slug
28
+
29
+ page = Rayo::Models::Page.new( @storage, self, @path + [slug] )
30
+ page = nil if page.file.nil? && page.directories.empty?
31
+
32
+ @children_cache[ slug ] = page
33
+ end
34
+
35
+ def directories
36
+ @directories ||= @storage.find_page_dirs( @parent.directories, @path.last )
37
+ end
38
+
39
+ def file
40
+ @file ||= @storage.find_page_file( @parent.directories, @path.last )
41
+ end
42
+
43
+ def parts
44
+ @parts ||= file ? @storage.find_page_parts( file ) : {}
45
+ end
46
+
47
+ def params
48
+ return @params if @params
49
+
50
+ segments = file[0..-(File.extname(file).length+1)].split(/[\/\\]/)[-@path.size..-1] || raise( "File doesn't correspond to path" )
51
+
52
+ @params = {}
53
+
54
+ segments.each_with_index do |segment,i|
55
+ @params[segment[1..-1]] = @path[i] if segment[0..0] == '%'
56
+ end
57
+
58
+ @params['path'] = path
59
+ @params
60
+ end
61
+
62
+ def context
63
+ return @context if @context
64
+
65
+ @context = @parent ? @parent.context.merge({ 'status' => 200 }) : { 'status' => 200 }
66
+ @context.merge! load_context( file ) if file
67
+ @context
68
+ end
69
+
70
+ def layout
71
+ @layout ||= @storage.layout( context['layout'] )
72
+ end
73
+
74
+ def []( key )
75
+ key = key.to_s
76
+
77
+ context[ key ] || params[ key ]
78
+ end
79
+
80
+ def render
81
+ layout.render( parser )
82
+ end
83
+
84
+ def parser
85
+ @parser ||= Radius::Parser.new( Rayo::TagContext.new( self ), :tag_prefix => 'r' )
86
+ end
87
+
88
+ private
89
+
90
+ def load_context( filename )
91
+ YAML::load( Erubis::Eruby.new( File.read( filename ) ).result( params ) )
92
+ end
93
+
94
+ end
@@ -0,0 +1,19 @@
1
+ class Rayo::Models::Renderable
2
+
3
+ attr_reader :file
4
+ attr_reader :filter
5
+
6
+ def initialize( file, filter )
7
+ @file = file
8
+ @filter = filter
9
+ end
10
+
11
+ def source
12
+ @source ||= File.read( file )
13
+ end
14
+
15
+ def render( parser )
16
+ filter.call( parser.parse( source ) )
17
+ end
18
+
19
+ end
@@ -0,0 +1,22 @@
1
+ require File.join(File.dirname(__FILE__), 'page.rb')
2
+ require File.join(File.dirname(__FILE__), 'status_page.rb')
3
+
4
+ class Rayo::Models::RootPage < Rayo::Models::Page
5
+
6
+ def initialize( storage )
7
+ super( storage, nil, [] )
8
+ end
9
+
10
+ def directories
11
+ [ @storage.config.directory :pages ]
12
+ end
13
+
14
+ def file
15
+ @file ||= @storage.find_page_file( directories, 'index' )
16
+ end
17
+
18
+ def params
19
+ @params ||= { 'path' => path }
20
+ end
21
+
22
+ end
@@ -0,0 +1,31 @@
1
+ require File.join(File.dirname(__FILE__), 'page.rb')
2
+
3
+ class Rayo::Models::StatusPage < Rayo::Models::Page
4
+
5
+ def initialize( storage, root, path, status )
6
+ super( storage, root, path )
7
+
8
+ @status = status
9
+ end
10
+
11
+ def directories
12
+ []
13
+ end
14
+
15
+ def file
16
+ @file ||= @storage.find_page_file( @parent.directories, @status.to_s )
17
+ end
18
+
19
+ def context
20
+ return @context if @context
21
+
22
+ @context = @parent ? @parent.context.merge({ 'status' => @status }) : { 'status' => @status }
23
+ @context.merge! load_context( file + '.yml' ) if file
24
+ @context
25
+ end
26
+
27
+ def params
28
+ { 'path' => path }
29
+ end
30
+
31
+ end
@@ -0,0 +1,127 @@
1
+ require File.join(File.dirname(__FILE__), 'models', 'root_page.rb')
2
+ require File.join(File.dirname(__FILE__), 'models', 'renderable.rb')
3
+
4
+ class Rayo::Storage
5
+
6
+ attr_reader :config
7
+
8
+ def initialize( config, lang )
9
+ @config = config
10
+
11
+ @lang = lang
12
+ @lang_prefix = '.' + lang
13
+
14
+ @layouts = {}
15
+ @snippets = {}
16
+ end
17
+
18
+ def snippet( name )
19
+ @snipetts[name.to_s] ||= find_renderable_file( :snippets, name.to_s ) || raise( "Snippet '#{name}' not found" )
20
+ end
21
+
22
+ def layout( name )
23
+ @layouts[name.to_s] ||= find_renderable( :layouts, name.to_s ) || raise( "Layout '#{name}' not found" )
24
+ end
25
+
26
+ def root_page
27
+ @root_page ||= Rayo::Models::RootPage.new( self )
28
+ end
29
+
30
+ def page( path )
31
+ root_page.descendant( path )
32
+ end
33
+
34
+ def status_page( path, status )
35
+ Rayo::Models::StatusPage.new( self, root_page, path, status )
36
+ end
37
+
38
+ def find_renderable( type, name )
39
+ if file = find_file( [config.directory( type )], name, config.renderable_exts )
40
+ renderable( file, File.extname( file ) )
41
+ end
42
+ end
43
+
44
+ def find_page_file( dirs, slug )
45
+ find_file( dirs, slug, config.page_exts )
46
+ end
47
+
48
+ def find_page_dirs( dirs, slug )
49
+ find_files dirs, slug, ['']
50
+ end
51
+
52
+ def find_pages( dirs )
53
+ find_files dirs, '*', config.page_exts + ['']
54
+ end
55
+
56
+ def find_page_parts( page_file )
57
+ parts = {}
58
+ page_file_base = File.basename( page_file, File.extname( page_file ) )
59
+ glob_files File.dirname( page_file ), page_file_base + '*', config.renderable_exts do |file,base,ext|
60
+ elems = base.split('.')
61
+
62
+ if elems.shift == page_file_base # Remove base (slug or variable)
63
+ if elems.size == 0 # There are no part name and language
64
+ parts[ 'body' ] ||= renderable( file, ext )
65
+ elsif elems.size == 1 # There are no language or no part name
66
+ parts[ elems[0] ] ||= renderable( file, ext )
67
+ parts[ 'body' ] ||= renderable( file, ext ) if elems[1] == @lang
68
+ else
69
+ parts[ elems[0] ] ||= renderable( file, ext ) if elems[1] == @lang
70
+ end
71
+ end
72
+ end
73
+ parts
74
+ end
75
+
76
+ private
77
+
78
+ def renderable( file, ext )
79
+ Rayo::Models::Renderable.new( file, config.filter( ext ) || raise( "Filter for '#{ext} not found" ) )
80
+ end
81
+
82
+ # Find first file with given name (or variable) and extension from given set
83
+ def find_file( dirs, name, exts )
84
+ glob_files( dirs, name, exts ) { |file,base,ext| return file }
85
+ nil
86
+ end
87
+
88
+ # Find files with given name (or variable) and extension from given set
89
+ def find_files( dirs, name, exts )
90
+ results = []
91
+ glob_files( dirs, name, exts ) { |file,base,ext| results << file }
92
+ results
93
+ end
94
+
95
+ def glob_files( dirs, name, exts, &block )
96
+ glob dirs, name + @lang_prefix, //, exts, &block # Search with given name and language
97
+ glob dirs, name, //, exts, &block # Search with given name without language
98
+ glob dirs, '%*' + @lang_prefix, /^%.+\.#{@lang}$/, exts, &block # Search with variable and language
99
+ glob dirs, '%*', /^%.+$/, exts, &block # Search with variable without language
100
+ end
101
+
102
+ def glob( dirs, mask_wo_ext, base_regexp, exts )
103
+ mask = mask_wo_ext + ext_mask( exts )
104
+
105
+ dirs.each do |dir|
106
+ Dir.glob File.join( dir, mask ) do |file|
107
+ ext = File.extname( file )
108
+ base = File.basename( file, ext )
109
+
110
+ yield file, base, ext if exts.include?( ext ) && base =~ base_regexp
111
+ end
112
+ end
113
+ end
114
+
115
+ def ext_mask( exts )
116
+ if exts.size == 1 # Only one extension
117
+ exts.first # Use it in mask
118
+ elsif !exts.include? '' # No empty extension
119
+ '.*' # Use any extension mask
120
+ elsif mask_wo_ext[-1..-1] == '*' # Mask ends with star
121
+ '' # No need for another star
122
+ else
123
+ '*' # Any prefix
124
+ end
125
+ end
126
+
127
+ end
@@ -0,0 +1,18 @@
1
+ class Rayo::TagContext < Radius::Context
2
+
3
+ attr_reader :page
4
+
5
+ def initialize(page)
6
+ super()
7
+
8
+ @page = page
9
+
10
+ globals.page = @page
11
+
12
+ tagger = @page.storage.config.create_tagger
13
+ tagger.methods.each do |name|
14
+ define_tag(name[4..-1]) { |tag_binding| tagger.send name, tag_binding } if name[0..3] == 'tag:'
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,19 @@
1
+ module Rayo::Taggable
2
+
3
+ module ClassMethods
4
+
5
+ def tag( name, &block )
6
+ define_method("tag:#{name}", &block)
7
+ end
8
+
9
+ end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+
15
+ def error( text )
16
+ "<strong class=\"error\">#{text}</strong>"
17
+ end
18
+
19
+ end
@@ -0,0 +1,46 @@
1
+ module Rayo::Tags::ContentTags
2
+
3
+ include Rayo::Taggable
4
+
5
+ tag 'content' do |tag|
6
+ part_name = tag.attr['part'] || 'body'
7
+ inherit = tag.attr['inherit'] == 'true'
8
+
9
+ page = tag.locals.page
10
+ part = page.parts[part_name]
11
+
12
+ while inherit && !part && page.parent do
13
+ page = page.parent
14
+ part = page.parts[part_name]
15
+ end
16
+
17
+ if part
18
+ part.render( tag.globals.page.parser )
19
+ else
20
+ error "No part '#{part_name}' found for page '#{tag.locals.page.path}'"
21
+ end
22
+ end
23
+
24
+ tag 'content_for_layout' do |tag|
25
+ send 'tag:content', tag
26
+ end
27
+
28
+ tag 'snippet' do |tag|
29
+ number = (tag.attr['times'] || '1').to_i
30
+ result = ''
31
+ number.times { result << tag.expand }
32
+ result
33
+ end
34
+
35
+ tag 'hello' do
36
+ 'Hello world'
37
+ end
38
+
39
+ tag 'repeat' do |tag|
40
+ number = (tag.attr['times'] || '1').to_i
41
+ result = ''
42
+ number.times { result << tag.expand }
43
+ result
44
+ end
45
+
46
+ end
@@ -0,0 +1,63 @@
1
+ module Rayo::Tags::NavigationTags
2
+
3
+ include Rayo::Taggable
4
+
5
+ tag 'find' do |tag|
6
+ url = tag.attr['url']
7
+ path = url.split('/')
8
+
9
+ if path.first.empty?
10
+ path.shift
11
+ page = tag.locals.page.storage.root_page
12
+ else
13
+ page = tag.locals.page
14
+ end
15
+
16
+ page = page.descendant( path )
17
+
18
+ if page
19
+ tag.locals.page = page
20
+ tag.expand
21
+ else
22
+ error "No page '#{url}' found"
23
+ end
24
+ end
25
+
26
+ tag 'children' do |tag|
27
+ tag.locals.children = tag.locals.page.children
28
+ tag.expand
29
+ end
30
+
31
+ tag 'children:count' do |tag|
32
+ tag.locals.children.size
33
+ end
34
+
35
+ tag 'children:first' do |tag|
36
+ if first = tag.locals.children.first
37
+ tag.locals.page = first
38
+ tag.expand
39
+ end
40
+ end
41
+
42
+ tag 'children:last' do |tag|
43
+ if first = tag.locals.children.last
44
+ tag.locals.page = first
45
+ tag.expand
46
+ end
47
+ end
48
+
49
+ tag 'children:each' do |tag|
50
+ result = ''
51
+
52
+ children = tag.locals.children
53
+ children.each_with_index do |item, i|
54
+ tag.locals.child = item
55
+ tag.locals.page = item
56
+ tag.locals.first_child = i == 0
57
+ tag.locals.last_child = i == children.length - 1
58
+ result << tag.expand
59
+ end
60
+ result
61
+ end
62
+
63
+ end
@@ -0,0 +1,25 @@
1
+ module Rayo::Tags::PropertyTags
2
+
3
+ include Rayo::Taggable
4
+
5
+ tag 'title' do |tag|
6
+ tag.locals.page.context['title']
7
+ end
8
+
9
+ tag 'path' do |tag|
10
+ curpath = tag.globals.page.path
11
+ path = tag.locals.page.path
12
+
13
+ i = 0
14
+ while i < path.size && i < curpath.size && path[i] == curpath[i] do
15
+ i = i + 1
16
+ end
17
+
18
+ '../' * (curpath.size - i) + path[i..-1].join('/')
19
+ end
20
+
21
+ tag 'link' do |tag|
22
+ "<a href=\"#{send 'tag:path', tag}\">#{tag.locals.page.context['title']}</a>"
23
+ end
24
+
25
+ end
@@ -0,0 +1,9 @@
1
+ module Rayo
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rayo
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Alexey Noskov
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-18 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: erubis
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: radius
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: sinatra
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 15
58
+ segments:
59
+ - 1
60
+ - 0
61
+ version: "1.0"
62
+ type: :runtime
63
+ version_requirements: *id003
64
+ description: It's lightweight CMS based on Sinatra framework, where data are stored in file system (so you may use Git power) and enhanced Radius gem.
65
+ email:
66
+ - alexey.noskov@gmail.com
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - lib/rayo.rb
75
+ - lib/rayo/storage.rb
76
+ - lib/rayo/application.rb
77
+ - lib/rayo/version.rb
78
+ - lib/rayo/taggable.rb
79
+ - lib/rayo/models/root_page.rb
80
+ - lib/rayo/models/page.rb
81
+ - lib/rayo/models/status_page.rb
82
+ - lib/rayo/models/renderable.rb
83
+ - lib/rayo/tags/property_tags.rb
84
+ - lib/rayo/tags/content_tags.rb
85
+ - lib/rayo/tags/navigation_tags.rb
86
+ - lib/rayo/config.rb
87
+ - lib/rayo/tag_context.rb
88
+ has_rdoc: true
89
+ homepage: http://github.com/alno/rayo
90
+ licenses: []
91
+
92
+ post_install_message:
93
+ rdoc_options: []
94
+
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 3
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ hash: 23
112
+ segments:
113
+ - 1
114
+ - 3
115
+ - 6
116
+ version: 1.3.6
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.3.7
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Lightweight CMS based on Sinatra and Radius
124
+ test_files: []
125
+