rayo 0.1.0 → 0.2.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,20 @@
1
+ Copyright (c) 2009 Alexey Noskov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,154 @@
1
+ = Rayo
2
+
3
+ Rayo is a CMS based on lightweight {Sinatra framework}[http://www.sinatrarb.com/] and {Radius gem}[http://radius.rubyforge.org/]. It was inspired by {Radiant}[http://radiantcms.org/], very powerful ROR-based CMS.
4
+
5
+ <b>Caution! The project in a very early stage of development!</b>
6
+
7
+ == Installation
8
+
9
+ Install rayo gem (recommended):
10
+
11
+ sudo gem install irwi
12
+
13
+ Or clone it:
14
+
15
+ git clone git://github.com/alno/rayo
16
+
17
+ == Rack application
18
+
19
+ To create basic Rayo-based application all you need is Rackup file (config.ru) with content similar to:
20
+
21
+ require '../rayo/lib/rayo.rb'
22
+
23
+ class Application < Rayo::Application
24
+
25
+ configure do |c|
26
+ c.content_dir = File.join( File.dirname(__FILE__), 'content' ) # Content location
27
+ c.languages = ['en','ru'] # Supported languages
28
+ end
29
+
30
+ end
31
+
32
+ run Application
33
+
34
+ == Content structure
35
+
36
+ Here we create our application by extending Rayo::Application class, set content directory location and supported languages.
37
+ Content should be placed in <tt>content</tt> subdirectories similar to following scheme:
38
+
39
+ content
40
+ + layouts
41
+ + base.html
42
+ + snippets
43
+ + footer.html
44
+ + pages
45
+ + index.yml
46
+ + index.html
47
+ + index.sidebar.ru.html
48
+ + index.sidebar.en.html
49
+ + section.yml
50
+ + section.html
51
+ + section
52
+ + subpage.yml
53
+ + subpage.html
54
+
55
+ There are example layout, snippet and 3 pages: <tt>index</tt>, <tt>section</tt> and <tt>section/subpage</tt>. Index page contains additional page part <tt>sidebar</tt>, wchih is translated to English and Russian.
56
+ It should be noticed what each page have corresponding .yml file. This file contains page properties which should be used in page rendering.
57
+
58
+ === Generic pages
59
+
60
+ You may create generic pages which are rendered for a set of different paths. For example to create archive pages in blog you may use:
61
+
62
+ pages
63
+ + 2010
64
+ | + 09
65
+ | + my-first-post.yml
66
+ | + my-first-post.html
67
+ + %year.yml
68
+ + %year.html
69
+ + %year
70
+ + %month.yml
71
+ + %month.html
72
+
73
+ == Content formatting
74
+
75
+ === Built-In Tags
76
+
77
+ === Filters
78
+
79
+ == Configuration
80
+
81
+ Configuration options are provided in <tt>configure</tt> block in application class:
82
+
83
+ configure do |c|
84
+ c.content_dir = File.join( File.dirname(__FILE__), 'content' ) # Content location
85
+ c.languages = ['en','ru'] # Supported languages
86
+ end
87
+
88
+ Besides of these options you append your filters for processing content (after expanding radius tags):
89
+
90
+ c.add_filter 'textile' do |source|
91
+ RedCloth.new( source ).to_html
92
+ end
93
+
94
+ Also, you may define modules which contain tag definitions:
95
+
96
+ module MyTags
97
+
98
+ include Rayo::Taggable
99
+
100
+ tag 'hello' do
101
+ 'Hello world'
102
+ end
103
+
104
+ tag 'repeat' do |tag|
105
+ number = (tag.attr['times'] || '1').to_i
106
+ result = ''
107
+ number.times { result << tag.expand }
108
+ result
109
+ end
110
+
111
+ end
112
+
113
+ And register it:
114
+
115
+ c.add_tags MyTags
116
+
117
+ === Multidomain applications
118
+
119
+ Sometimes you may want to serve different domains with one application sharing layouts and snippets. Rayo has built-in support for this scenario. All you need is to create subdirectories in pages for different domains and add some lines to <tt>configure</tt> block:
120
+
121
+ To serve 'first.example.com' with pages from <tt>content/pages/first.example.com</tt> directory:
122
+
123
+ c.add_domain 'first.example.com'
124
+
125
+ To serve 'second.example.com' or 'www.second.example.com' with pages from <tt>content/pages/second</tt> directory:
126
+
127
+ c.add_domain 'second.', /^(www\.)?second\.example\.com$/
128
+
129
+ With both lines corresponding directory structure should be:
130
+
131
+ content
132
+ + layouts
133
+ + snippets
134
+ + pages
135
+ + first.example.com
136
+ + second
137
+
138
+ That's all you need to build multi-domain application.
139
+
140
+ == Planned features
141
+
142
+ * Support for sites without I18n
143
+ * Support for different page formats
144
+ * Drop-in tag libary support
145
+ * Support for pages without .yml
146
+ * Generator which creates a set of static pages from content structure
147
+
148
+ == Contributors
149
+
150
+ * Alexey Noskov (http://github.com/alno)
151
+
152
+ Feel free to add yourself when you add new features.
153
+
154
+ Copyright (c) 2010 Alexey Noskov, released under the MIT license
@@ -7,7 +7,7 @@ class Rayo::Application < Sinatra::Base
7
7
 
8
8
  class << self
9
9
 
10
- attr_accessor :config # Application configuration object
10
+ attr_accessor :config
11
11
 
12
12
  # Configure application
13
13
  def configure
@@ -20,35 +20,41 @@ class Rayo::Application < Sinatra::Base
20
20
  self.class.config
21
21
  end
22
22
 
23
- get '/' do
24
- redirect_to_lang '' # Root page
25
- end
23
+ get '/*' do |path|
24
+ cfg = config.domain( request.host ) # Config for current host
26
25
 
27
- get '*/' do |path|
28
- redirect path # Remove trailing slashes
29
- end
26
+ if cfg
27
+ path = path.split '/' # Split path into segments
30
28
 
31
- get '/*' do |path|
32
- path = path.split '/' # Split path into segments
29
+ empty_segments_found = path.reject! {|e| e.empty? } # Clear path and detect empty segments
33
30
 
34
- return redirect_to_lang path unless config.languages.include? path.first
31
+ return redirect_to_lang path unless config.languages.include? path.first
32
+ return redirect '/' + path.join('/') if empty_segments_found
35
33
 
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
34
+ lang = path.shift # Determine language
40
35
 
41
- [ page[:status], page.render ] # Return page status and content
36
+ storage = create_storage( cfg ) # Page storage
37
+ page = storage.page( lang, path ) # Find page by path
38
+ page = storage.status_page( lang, path, 404 ) unless page && page.file # Render 404 page if there are no page, or there are no file
39
+
40
+ [ page[:status], page.render ] # Return page status and content
41
+ else
42
+ [ 404, "Page not found" ]
43
+ end
42
44
  end
43
45
 
44
46
  private
45
47
 
48
+ def create_storage( cfg )
49
+ Rayo::Storage.new( cfg )
50
+ end
51
+
46
52
  def select_language
47
53
  config.languages.first
48
54
  end
49
55
 
50
56
  def redirect_to_lang( path )
51
- redirect [select_language, *path].join '/'
57
+ redirect '/' + [ select_language, *path].join('/')
52
58
  end
53
59
 
54
60
  end
@@ -14,6 +14,7 @@ class Rayo::Config
14
14
  @page_exts = ['.yml']
15
15
 
16
16
  @filters = {}
17
+ @domains = []
17
18
 
18
19
  # Default filters
19
20
  add_filter('html'){|source| source }
@@ -28,15 +29,28 @@ class Rayo::Config
28
29
  end
29
30
 
30
31
  # Add tags defined in module
32
+ #
33
+ # @param [Module] module defining tags to use
31
34
  def add_tags( tag_module )
32
35
  @tagger.extend tag_module
33
36
  end
34
37
 
35
38
  # Add filter
39
+ #
40
+ # @param [String,Symbol] file extension
41
+ # @param [Proc] filter proc which accepts source and return it in processed form
36
42
  def add_filter( ext, &filter )
37
43
  @filters[".#{ext}"] = filter
38
44
  end
39
45
 
46
+ # Add domain
47
+ #
48
+ # @param [String] domain name
49
+ # @param [Regexp,String] host pattern
50
+ def add_domain( name, exp = nil )
51
+ @domains << Rayo::Config::Domain.new( self, name, exp )
52
+ end
53
+
40
54
  # Create new object containing all defined tags
41
55
  def create_tagger
42
56
  @tagger.clone
@@ -54,4 +68,14 @@ class Rayo::Config
54
68
  @filters[ ext.to_s ]
55
69
  end
56
70
 
71
+ def domain( host )
72
+ if @domains.empty?
73
+ self # No multidomain support
74
+ else
75
+ @domains.find { |domain| domain.matches? host }
76
+ end
77
+ end
78
+
57
79
  end
80
+
81
+ require File.join(File.dirname(__FILE__), 'config/domain.rb')
@@ -0,0 +1,27 @@
1
+ require 'forwardable'
2
+
3
+ class Rayo::Config::Domain
4
+
5
+ extend Forwardable
6
+
7
+ def_delegators :@parent, :create_tagger, :languages, :page_exts, :renderable_exts, :filter
8
+
9
+ def initialize( parent, name, exp )
10
+ @parent = parent
11
+ @name = name
12
+ @exp = exp || Regexp.new( "^#{Regexp.quote( name )}\.?$" )
13
+ end
14
+
15
+ def matches?( host )
16
+ host =~ @exp
17
+ end
18
+
19
+ def directory( content_type )
20
+ if content_type == :pages
21
+ File.join( @parent.directory( content_type ), @name )
22
+ else
23
+ @parent.directory( content_type )
24
+ end
25
+ end
26
+
27
+ end
@@ -6,42 +6,59 @@ require File.join(File.dirname(__FILE__), '..', 'tag_context.rb')
6
6
 
7
7
  class Rayo::Models::Page
8
8
 
9
- attr_reader :storage, :parent, :path
9
+ attr_reader :storage, :parent, :lang, :path
10
10
 
11
- def initialize( storage, parent, path )
11
+ def initialize( storage, parent, lang, path )
12
12
  @storage = storage
13
13
  @parent = parent
14
+ @lang = lang
14
15
  @path = path
15
16
  end
16
17
 
17
18
  def descendant( relative_path )
18
- relative_path.inject( self ) {|page,slug| page.child( slug ) }
19
+ relative_path.inject( self ) do |page,slug|
20
+ return nil unless page
21
+
22
+ page.child( slug )
23
+ end
19
24
  end
20
25
 
21
26
  def children
22
- @children ||= @storage.find_pages( directories ).map{|name| child( name ) }
27
+ @children ||= @storage.find_pages( directories, @lang ).map{|name| child( name ) }
23
28
  end
24
29
 
25
30
  def child( slug )
26
31
  @children_cache ||= {}
27
32
  return @children_cache[ slug ] if @children_cache.include? slug
28
33
 
29
- page = Rayo::Models::Page.new( @storage, self, @path + [slug] )
34
+ page = Rayo::Models::Page.new( @storage, self, @lang, @path + [slug] )
30
35
  page = nil if page.file.nil? && page.directories.empty?
31
36
 
32
37
  @children_cache[ slug ] = page
33
38
  end
34
39
 
35
40
  def directories
36
- @directories ||= @storage.find_page_dirs( @parent.directories, @path.last )
41
+ @directories ||= @storage.find_page_dirs( @parent.directories, @lang, @path.last )
37
42
  end
38
43
 
39
44
  def file
40
- @file ||= @storage.find_page_file( @parent.directories, @path.last )
45
+ @file ||= @storage.find_page_file( @parent.directories, @lang, @path.last )
41
46
  end
42
47
 
43
48
  def parts
44
- @parts ||= file ? @storage.find_page_parts( file ) : {}
49
+ @parts ||= file ? @storage.find_page_parts( file, @lang ) : {}
50
+ end
51
+
52
+ def find_part( part_name, inherit = false )
53
+ page = self
54
+ part = self.parts[part_name]
55
+
56
+ while inherit && !part && page.parent do
57
+ page = page.parent
58
+ part = page.parts[part_name]
59
+ end
60
+
61
+ part
45
62
  end
46
63
 
47
64
  def params
@@ -68,7 +85,7 @@ class Rayo::Models::Page
68
85
  end
69
86
 
70
87
  def layout
71
- @layout ||= @storage.layout( context['layout'] )
88
+ @layout ||= @storage.layout( @lang, context['layout'] )
72
89
  end
73
90
 
74
91
  def []( key )
@@ -88,7 +105,7 @@ class Rayo::Models::Page
88
105
  private
89
106
 
90
107
  def load_context( filename )
91
- YAML::load( Erubis::Eruby.new( File.read( filename ) ).result( params ) )
108
+ YAML::load( Erubis::Eruby.new( @storage.load( filename ) ).result( params ) )
92
109
  end
93
110
 
94
111
  end
@@ -1,15 +1,17 @@
1
1
  class Rayo::Models::Renderable
2
2
 
3
+ attr_reader :storage
3
4
  attr_reader :file
4
5
  attr_reader :filter
5
6
 
6
- def initialize( file, filter )
7
+ def initialize( storage, file, filter )
8
+ @storage = storage
7
9
  @file = file
8
10
  @filter = filter
9
11
  end
10
12
 
11
13
  def source
12
- @source ||= File.read( file )
14
+ @source ||= @storage.load( file )
13
15
  end
14
16
 
15
17
  def render( parser )
@@ -3,8 +3,8 @@ require File.join(File.dirname(__FILE__), 'status_page.rb')
3
3
 
4
4
  class Rayo::Models::RootPage < Rayo::Models::Page
5
5
 
6
- def initialize( storage )
7
- super( storage, nil, [] )
6
+ def initialize( storage, lang )
7
+ super( storage, nil, lang, [] )
8
8
  end
9
9
 
10
10
  def directories
@@ -12,7 +12,7 @@ class Rayo::Models::RootPage < Rayo::Models::Page
12
12
  end
13
13
 
14
14
  def file
15
- @file ||= @storage.find_page_file( directories, 'index' )
15
+ @file ||= @storage.find_page_file( directories, @lang, 'index' )
16
16
  end
17
17
 
18
18
  def params
@@ -3,7 +3,7 @@ require File.join(File.dirname(__FILE__), 'page.rb')
3
3
  class Rayo::Models::StatusPage < Rayo::Models::Page
4
4
 
5
5
  def initialize( storage, root, path, status )
6
- super( storage, root, path )
6
+ super( storage, root, root.lang, path )
7
7
 
8
8
  @status = status
9
9
  end
@@ -13,14 +13,14 @@ class Rayo::Models::StatusPage < Rayo::Models::Page
13
13
  end
14
14
 
15
15
  def file
16
- @file ||= @storage.find_page_file( @parent.directories, @status.to_s )
16
+ @file ||= @storage.find_page_file( @parent.directories, @lang, @status.to_s )
17
17
  end
18
18
 
19
19
  def context
20
20
  return @context if @context
21
21
 
22
22
  @context = @parent ? @parent.context.merge({ 'status' => @status }) : { 'status' => @status }
23
- @context.merge! load_context( file + '.yml' ) if file
23
+ @context.merge! load_context( file ) if file
24
24
  @context
25
25
  end
26
26
 
@@ -5,105 +5,134 @@ class Rayo::Storage
5
5
 
6
6
  attr_reader :config
7
7
 
8
- def initialize( config, lang )
8
+ def initialize( config )
9
9
  @config = config
10
10
 
11
- @lang = lang
12
- @lang_prefix = '.' + lang
13
-
11
+ @roots = {}
14
12
  @layouts = {}
15
13
  @snippets = {}
16
14
  end
17
15
 
18
- def snippet( name )
19
- @snipetts[name.to_s] ||= find_renderable_file( :snippets, name.to_s ) || raise( "Snippet '#{name}' not found" )
16
+ def snippet( lang, name )
17
+ @snippets["#{lang}|#{name}"] ||= find_renderable( :snippets, lang, name.to_s ) || raise( "Snippet '#{name}' not found" )
20
18
  end
21
19
 
22
- def layout( name )
23
- @layouts[name.to_s] ||= find_renderable( :layouts, name.to_s ) || raise( "Layout '#{name}' not found" )
20
+ def layout( lang, name )
21
+ @layouts["#{lang}|#{name}"] ||= find_renderable( :layouts, lang, name.to_s ) || raise( "Layout '#{name}' not found" )
24
22
  end
25
23
 
26
- def root_page
27
- @root_page ||= Rayo::Models::RootPage.new( self )
24
+ # Retrieves root page for specific language
25
+ #
26
+ # @param [String,Symbol] page language
27
+ # @return [Rayo::Models::RootPage] root page
28
+ def root_page( lang )
29
+ @roots[ lang.to_s ] ||= Rayo::Models::RootPage.new( self, lang.to_s )
28
30
  end
29
31
 
30
- def page( path )
31
- root_page.descendant( path )
32
+ # Retrieves page for specific language and path
33
+ #
34
+ # @param [String,Symbol] page language
35
+ # @param [Array<String>] page path
36
+ # @return [Rayo::Models::Page] page
37
+ def page( lang, path )
38
+ root_page( lang ).descendant( path )
32
39
  end
33
40
 
34
- def status_page( path, status )
35
- Rayo::Models::StatusPage.new( self, root_page, path, status )
41
+ # Retrieves status page for specific language and path
42
+ #
43
+ # @param [String,Symbol] page language
44
+ # @param [Array<String>] page path
45
+ # @return [Rayo::Models::StatusPage] status page
46
+ def status_page( lang, path, status )
47
+ Rayo::Models::StatusPage.new( self, root_page( lang ), path, status )
36
48
  end
37
49
 
38
- def find_renderable( type, name )
39
- if file = find_file( [config.directory( type )], name, config.renderable_exts )
50
+ def find_renderable( type, lang, name )
51
+ if file = find_file( [config.directory( type )], lang, name, config.renderable_exts )
40
52
  renderable( file, File.extname( file ) )
41
53
  end
42
54
  end
43
55
 
44
- def find_page_file( dirs, slug )
45
- find_file( dirs, slug, config.page_exts )
56
+ def find_page_file( dirs, lang, slug )
57
+ find_file( dirs, lang, slug, config.page_exts )
46
58
  end
47
59
 
48
- def find_page_dirs( dirs, slug )
49
- find_files dirs, slug, ['']
60
+ def find_page_dirs( dirs, lang, slug )
61
+ find_files( dirs, lang, slug, [''] )
50
62
  end
51
63
 
52
- def find_pages( dirs )
53
- find_files dirs, '*', config.page_exts + ['']
64
+ def find_pages( dirs, lang )
65
+ res = []
66
+ glob_files dirs, lang, '*', config.page_exts + [''] do |file,base,ext|
67
+ elems = base.split('.')
68
+ elem_name = elems[0]
69
+ elem_lang = elems[1]
70
+
71
+ res << elem_name unless elem_name[0..0] == '%' || (dirs == ([ @config.directory :pages ]) && (elem_name == 'index' || elem_name =~ /^\d+$/ )) || (elem_lang && elem_lang != lang)
72
+ end
73
+ res.uniq
54
74
  end
55
75
 
56
- def find_page_parts( page_file )
76
+ def find_page_parts( page_file, lang )
57
77
  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|
78
+ page_file_base = File.basename( page_file ).split('.').first
79
+ glob_files File.dirname( page_file ), lang, page_file_base + '*', config.renderable_exts do |file,base,ext|
60
80
  elems = base.split('.')
61
81
 
62
82
  if elems.shift == page_file_base # Remove base (slug or variable)
63
83
  if elems.size == 0 # There are no part name and language
64
84
  parts[ 'body' ] ||= renderable( file, ext )
65
85
  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
86
+ if elems[0] == lang
87
+ parts[ 'body' ] ||= renderable( file, ext )
88
+ else
89
+ parts[ elems[0] ] ||= renderable( file, ext )
90
+ end
68
91
  else
69
- parts[ elems[0] ] ||= renderable( file, ext ) if elems[1] == @lang
92
+ parts[ elems[0] ] ||= renderable( file, ext ) if elems[1] == lang
70
93
  end
71
94
  end
72
95
  end
73
96
  parts
74
97
  end
75
98
 
99
+ def load( file )
100
+ File.read( file )
101
+ end
102
+
76
103
  private
77
104
 
78
105
  def renderable( file, ext )
79
- Rayo::Models::Renderable.new( file, config.filter( ext ) || raise( "Filter for '#{ext} not found" ) )
106
+ Rayo::Models::Renderable.new( self, file, config.filter( ext ) || raise( "Filter for '#{ext} not found" ) )
80
107
  end
81
108
 
82
109
  # 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 }
110
+ def find_file( dirs, lang, name, exts )
111
+ glob_files( dirs, lang, name, exts ) { |file,base,ext| return file }
85
112
  nil
86
113
  end
87
114
 
88
115
  # Find files with given name (or variable) and extension from given set
89
- def find_files( dirs, name, exts )
116
+ def find_files( dirs, lang, name, exts )
90
117
  results = []
91
- glob_files( dirs, name, exts ) { |file,base,ext| results << file }
118
+ glob_files( dirs, lang, name, exts ) { |file,base,ext| results << file }
92
119
  results
93
120
  end
94
121
 
95
- def glob_files( dirs, name, exts, &block )
96
- glob dirs, name + @lang_prefix, //, exts, &block # Search with given name and language
122
+ def glob_files( dirs, lang, name, exts, &block )
123
+ lang_prefix = "." + lang
124
+
125
+ glob dirs, name + lang_prefix, //, exts, &block # Search with given name and language
97
126
  glob dirs, name, //, exts, &block # Search with given name without language
98
- glob dirs, '%*' + @lang_prefix, /^%.+\.#{@lang}$/, exts, &block # Search with variable and language
127
+ glob dirs, '%*' + lang_prefix, /^%.+\.#{lang}$/, exts, &block # Search with variable and language
99
128
  glob dirs, '%*', /^%.+$/, exts, &block # Search with variable without language
100
129
  end
101
130
 
102
131
  def glob( dirs, mask_wo_ext, base_regexp, exts )
103
- mask = mask_wo_ext + ext_mask( exts )
132
+ mask = mask_wo_ext + ext_mask( mask_wo_ext, exts )
104
133
 
105
134
  dirs.each do |dir|
106
- Dir.glob File.join( dir, mask ) do |file|
135
+ dir_glob File.join( dir, mask ) do |file|
107
136
  ext = File.extname( file )
108
137
  base = File.basename( file, ext )
109
138
 
@@ -112,7 +141,11 @@ class Rayo::Storage
112
141
  end
113
142
  end
114
143
 
115
- def ext_mask( exts )
144
+ def dir_glob( mask, &block )
145
+ Dir.glob mask, &block
146
+ end
147
+
148
+ def ext_mask( mask_wo_ext, exts )
116
149
  if exts.size == 1 # Only one extension
117
150
  exts.first # Use it in mask
118
151
  elsif !exts.include? '' # No empty extension
@@ -8,6 +8,7 @@ class Rayo::TagContext < Radius::Context
8
8
  @page = page
9
9
 
10
10
  globals.page = @page
11
+ globals.storage = @page.storage
11
12
 
12
13
  tagger = @page.storage.config.create_tagger
13
14
  tagger.methods.each do |name|
@@ -15,4 +16,20 @@ class Rayo::TagContext < Radius::Context
15
16
  end
16
17
  end
17
18
 
19
+ def render_tag(name, attributes = {}, &block)
20
+ super
21
+ rescue Exception => e
22
+ error("#{e.message} <pre>#{e.backtrace.join("\n")}</pre>")
23
+ end
24
+
25
+ def tag_missing(name, attributes = {}, &block)
26
+ super
27
+ rescue Radius::UndefinedTagError => e
28
+ error("#{e.message} <pre>#{e.backtrace.join("\n")}</pre>")
29
+ end
30
+
31
+ def error( text )
32
+ "<strong class=\"error\">#{text}</strong>"
33
+ end
34
+
18
35
  end
@@ -3,44 +3,55 @@ module Rayo::Tags::ContentTags
3
3
  include Rayo::Taggable
4
4
 
5
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
6
+ if part = find_part( tag )
18
7
  part.render( tag.globals.page.parser )
19
8
  else
20
- error "No part '#{part_name}' found for page '#{tag.locals.page.path}'"
9
+ error "No part '#{tag.attr['part'] || 'body'}' found for page '#{tag.locals.page.path.join('/')}'"
21
10
  end
22
11
  end
23
12
 
13
+ tag 'if_content' do |tag|
14
+ find_part( tag ) && tag.expand || ''
15
+ end
16
+
24
17
  tag 'content_for_layout' do |tag|
25
- send 'tag:content', tag
18
+ tag.globals.content_stack ||= [] # Prepare the stacks
19
+ tag.globals.content_stack.pop || send( 'tag:content', tag )
26
20
  end
27
21
 
28
- tag 'snippet' do |tag|
29
- number = (tag.attr['times'] || '1').to_i
30
- result = ''
31
- number.times { result << tag.expand }
32
- result
22
+ tag 'layout' do |tag|
23
+ if name = tag.attr['name'].strip
24
+ tag.globals.layout_stack ||= [] # Prepare layout stack
25
+ tag.globals.content_stack ||= [] # Prepare content stack
26
+
27
+ if layout = tag.globals.storage.layout( tag.globals.page.lang, name )
28
+ tag.globals.layout_stack << name # Track this layout on the stack
29
+ tag.globals.content_stack << tag.expand # Save contents of inside_layout for later insertion
30
+
31
+ layout.render( tag.globals.page.parser )
32
+ else
33
+ error "Parent layout '#{name.strip}' not found for 'layout' tag"
34
+ end
35
+ else
36
+ error "'layout' tag must contain a 'name' attribute"
37
+ end
33
38
  end
34
39
 
35
- tag 'hello' do
36
- 'Hello world'
40
+ tag 'snippet' do |tag|
41
+ snippet_name = tag.attr['name']
42
+ snippet = tag.globals.storage.snippet( tag.globals.page.lang, snippet_name )
43
+
44
+ if snippet
45
+ snippet.render( tag.globals.page.parser )
46
+ else
47
+ error "No snippet '#{snippet_name}' found"
48
+ end
37
49
  end
38
50
 
39
- tag 'repeat' do |tag|
40
- number = (tag.attr['times'] || '1').to_i
41
- result = ''
42
- number.times { result << tag.expand }
43
- result
51
+ private
52
+
53
+ def find_part( tag )
54
+ tag.locals.page.find_part( tag.attr['part'] || 'body', tag.attr['inherit'] == 'true' )
44
55
  end
45
56
 
46
57
  end
@@ -8,7 +8,7 @@ module Rayo::Tags::NavigationTags
8
8
 
9
9
  if path.first.empty?
10
10
  path.shift
11
- page = tag.locals.page.storage.root_page
11
+ page = tag.locals.page.storage.root_page( tag.globals.page.lang )
12
12
  else
13
13
  page = tag.locals.page
14
14
  end
@@ -6,20 +6,48 @@ module Rayo::Tags::PropertyTags
6
6
  tag.locals.page.context['title']
7
7
  end
8
8
 
9
+ tag 'description' do |tag|
10
+ tag.locals.page.context['description']
11
+ end
12
+
9
13
  tag 'path' do |tag|
10
- curpath = tag.globals.page.path
14
+ basepath = tag.globals.page.path
11
15
  path = tag.locals.page.path
12
16
 
13
- i = 0
14
- while i < path.size && i < curpath.size && path[i] == curpath[i] do
15
- i = i + 1
16
- end
17
+ if basepath.empty?
18
+ [tag.locals.page.lang, *path].join('/')
19
+ else
20
+ basepath = basepath[0..-2]
21
+
22
+ i = 0
23
+ while i < path.size && i < basepath.size && path[i] == basepath[i] do
24
+ i = i + 1
25
+ end
17
26
 
18
- '../' * (curpath.size - i) + path[i..-1].join('/')
27
+ '../' * (basepath.size - i) + path[i..-1].join('/')
28
+ end
19
29
  end
20
30
 
21
31
  tag 'link' do |tag|
22
- "<a href=\"#{send 'tag:path', tag}\">#{tag.locals.page.context['title']}</a>"
32
+ "<a href=\"#{send 'tag:path', tag}\">#{tag.single? ? send( 'tag:title', tag ) : tag.expand}</a>"
33
+ end
34
+
35
+ tag 'if_url' do |tag|
36
+ if_url( tag ) && tag.expand || ''
37
+ end
38
+
39
+ tag 'unless_url' do |tag|
40
+ if_url( tag ) && '' || tag.expand
41
+ end
42
+
43
+ tag 'date' do |tag|
44
+ Time.now.strftime(tag.attr['format'] || '%A, %B %d, %Y')
45
+ end
46
+
47
+ private
48
+
49
+ def if_url( tag )
50
+ tag.locals.page.path =~ Regexp.new( tag.attr['matches'] )
23
51
  end
24
52
 
25
53
  end
@@ -1,7 +1,7 @@
1
1
  module Rayo
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 1
4
+ MINOR = 2
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rayo
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Alexey Noskov
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-18 00:00:00 +04:00
18
+ date: 2010-11-02 00:00:00 +03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -61,17 +61,48 @@ dependencies:
61
61
  version: "1.0"
62
62
  type: :runtime
63
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.
64
+ - !ruby/object:Gem::Dependency
65
+ name: rspec
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 2
75
+ - 0
76
+ version: "2.0"
77
+ type: :development
78
+ version_requirements: *id004
79
+ - !ruby/object:Gem::Dependency
80
+ name: rack-test
81
+ prerelease: false
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ type: :development
92
+ version_requirements: *id005
93
+ description: Lightweight CMS based on Sinatra framework, where data are stored in file system (and so may be Git-powered) and enhanced using Radius gem.
65
94
  email:
66
95
  - alexey.noskov@gmail.com
67
96
  executables: []
68
97
 
69
98
  extensions: []
70
99
 
71
- extra_rdoc_files: []
72
-
100
+ extra_rdoc_files:
101
+ - README.rdoc
102
+ - MIT-LICENSE
73
103
  files:
74
104
  - lib/rayo.rb
105
+ - lib/rayo/config/domain.rb
75
106
  - lib/rayo/storage.rb
76
107
  - lib/rayo/application.rb
77
108
  - lib/rayo/version.rb
@@ -85,13 +116,20 @@ files:
85
116
  - lib/rayo/tags/navigation_tags.rb
86
117
  - lib/rayo/config.rb
87
118
  - lib/rayo/tag_context.rb
119
+ - MIT-LICENSE
120
+ - README.rdoc
88
121
  has_rdoc: true
89
122
  homepage: http://github.com/alno/rayo
90
123
  licenses: []
91
124
 
92
125
  post_install_message:
93
- rdoc_options: []
94
-
126
+ rdoc_options:
127
+ - --line-numbers
128
+ - --inline-source
129
+ - --title
130
+ - Rayo CMS
131
+ - --main
132
+ - README.rdoc
95
133
  require_paths:
96
134
  - lib
97
135
  required_ruby_version: !ruby/object:Gem::Requirement