rayo 0.1.0 → 0.2.0

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