rayo 0.1.0

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