rayo 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -74,6 +74,10 @@ You may create generic pages which are rendered for a set of different paths. Fo
74
74
 
75
75
  === Built-In Tags
76
76
 
77
+ ==== Hidden pages
78
+
79
+ If a page shouldn't be listed in children of parent page you may set property <tt>hidden</tt> to <tt>true</tt> in its yml file.
80
+
77
81
  === Filters
78
82
 
79
83
  == Configuration
@@ -114,7 +118,7 @@ And register it:
114
118
 
115
119
  c.add_tags MyTags
116
120
 
117
- === Multidomain applications
121
+ === Multidomain setup
118
122
 
119
123
  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
124
 
@@ -137,10 +141,19 @@ With both lines corresponding directory structure should be:
137
141
 
138
142
  That's all you need to build multi-domain application.
139
143
 
144
+ == Caching
145
+
146
+ Rayo supports transparent caching of your generated pages in filesystem which is configured by one line, specifying cache directory:
147
+
148
+ c.cache_dir = 'your cache directory'
149
+
150
+ Every page after request are written to corresponding file in cache directory, so next requests will be served from there. And if you configure your web server to search for files in cache directory ruby application will not be touched at all.
151
+
152
+ For single-domain setups it may be useful to point <tt>cache_dir</tt> to the public location, so it will be served by web-server without additional configuration.
153
+
140
154
  == Planned features
141
155
 
142
156
  * Support for sites without I18n
143
- * Support for different page formats
144
157
  * Drop-in tag libary support
145
158
  * Support for pages without .yml
146
159
  * Generator which creates a set of static pages from content structure
@@ -1,4 +1,5 @@
1
1
  require 'sinatra/base'
2
+ require 'ftools'
2
3
 
3
4
  require File.join(File.dirname(__FILE__), 'storage.rb')
4
5
  require File.join(File.dirname(__FILE__), 'config.rb')
@@ -20,31 +21,81 @@ class Rayo::Application < Sinatra::Base
20
21
  self.class.config
21
22
  end
22
23
 
23
- get '/*' do |path|
24
- cfg = config.domain( request.host ) # Config for current host
24
+ get '/*' do |url|
25
+ if cfg = config.domain( request.host ) # Config for current host
26
+ redir, lang, path, format = analyze_path url # Analyze path
25
27
 
26
- if cfg
27
- path = path.split '/' # Split path into segments
28
+ redirect_to_page( lang || select_language, path, format ) if redir # Redirect if needed
28
29
 
29
- empty_segments_found = path.reject! {|e| e.empty? } # Clear path and detect empty segments
30
+ format ||= cfg.default_format # Set default format if needed
30
31
 
31
- return redirect_to_lang path unless config.languages.include? path.first
32
- return redirect '/' + path.join('/') if empty_segments_found
32
+ cache_page cfg, lang, path, format do
33
+ storage = create_storage( cfg ) # Page storage
33
34
 
34
- lang = path.shift # Determine language
35
+ page = storage.page( lang, path ) # Find page by path
36
+ page = storage.status_page( lang, path, 404 ) unless page && page.file # Render 404 page if there are no page, or there are no file
35
37
 
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
38
+ render_page page, format # Render found page with given format
39
+ end
41
40
  else
42
- [ 404, "Page not found" ]
41
+ not_found 'Page not found'
43
42
  end
44
43
  end
45
44
 
46
45
  private
47
46
 
47
+ def render_page( page, format )
48
+ status page[:status] # Set response status
49
+ body page.render( format ) # Set response body
50
+
51
+ content_type format # Set Content-Type header
52
+ expires page[:expires] if page[:expires] # Set expires header
53
+ end
54
+
55
+ def redirect_to_page( lang, path, format )
56
+ url = '/' + [ lang || select_language, *path ].join('/')
57
+ url << '.' + format if format
58
+
59
+ redirect url
60
+ end
61
+
62
+ def cache_page( cfg, lang, path, format )
63
+ return yield unless cfg.cache_dir # Return block result if no cache directory set up
64
+
65
+ if path.empty?
66
+ file_path = File.join( cfg.cache_dir, lang, "index.#{format}" )
67
+ else
68
+ file_path = File.join( cfg.cache_dir, lang, path[0..-2], "#{path.last}.#{format}" )
69
+ end
70
+
71
+ if File.exists? file_path # If cached page exists
72
+ content_type format # Set Content-Type header
73
+ body File.read( file_path ) # Return its content as a body
74
+ else
75
+ yield # Render page
76
+
77
+ FileUtils.mkdir_p( File.dirname file_path ) # Create directory
78
+ File.open( file_path, 'w' ){|f| f.write body } # Write content
79
+ end
80
+ end
81
+
82
+ def analyze_path( p )
83
+ path = p.split '/' # Split path into segments
84
+ redir = path.reject! {|e| e.empty? } # Clear path and detect empty segments
85
+ redir ||= p[-1..-1] == '/'
86
+
87
+ unless path.empty? # If path non-empty
88
+ if m = path.last.match( /^(.*)\.([^.]+)$/ ) # Detect format
89
+ format = m[2]
90
+ path[-1] = m[1]
91
+ end
92
+
93
+ lang = path.shift if config.languages.include? path.first # Detect language
94
+ end
95
+
96
+ [redir || lang.nil?, lang, path, format]
97
+ end
98
+
48
99
  def create_storage( cfg )
49
100
  Rayo::Storage.new( cfg )
50
101
  end
@@ -53,8 +104,4 @@ class Rayo::Application < Sinatra::Base
53
104
  config.languages.first
54
105
  end
55
106
 
56
- def redirect_to_lang( path )
57
- redirect '/' + [ select_language, *path].join('/')
58
- end
59
-
60
107
  end
@@ -6,19 +6,21 @@ require File.join(File.dirname(__FILE__), 'tags/navigation_tags.rb')
6
6
  class Rayo::Config
7
7
 
8
8
  attr_accessor :content_dir
9
+ attr_accessor :cache_dir
10
+
9
11
  attr_accessor :languages
10
12
  attr_accessor :page_exts
11
13
 
14
+ attr_accessor :default_format
15
+ attr_accessor :default_domain
16
+
12
17
  def initialize
13
18
  @languages = ['en']
14
19
  @page_exts = ['.yml']
15
20
 
16
- @filters = {}
21
+ @formats = {}
17
22
  @domains = []
18
23
 
19
- # Default filters
20
- add_filter('html'){|source| source }
21
-
22
24
  @tagger = Object.new
23
25
  @tagger.extend Rayo::Taggable
24
26
 
@@ -26,6 +28,9 @@ class Rayo::Config
26
28
  add_tags Rayo::Tags::PropertyTags
27
29
  add_tags Rayo::Tags::ContentTags
28
30
  add_tags Rayo::Tags::NavigationTags
31
+
32
+ # Default format
33
+ @default_format = 'html'
29
34
  end
30
35
 
31
36
  # Add tags defined in module
@@ -35,14 +40,6 @@ class Rayo::Config
35
40
  @tagger.extend tag_module
36
41
  end
37
42
 
38
- # Add filter
39
- #
40
- # @param [String,Symbol] file extension
41
- # @param [Proc] filter proc which accepts source and return it in processed form
42
- def add_filter( ext, &filter )
43
- @filters[".#{ext}"] = filter
44
- end
45
-
46
43
  # Add domain
47
44
  #
48
45
  # @param [String] domain name
@@ -60,22 +57,30 @@ class Rayo::Config
60
57
  File.join( @content_dir, content_type.to_s )
61
58
  end
62
59
 
63
- def renderable_exts
64
- @filters.keys
60
+ # Add filter
61
+ #
62
+ # @param [String,Symbol] renderable file extension
63
+ # @param [String,Symbol] requested content format
64
+ # @param [Proc] filter proc which accepts source and return it in processed form
65
+ def add_filter( from, to = nil, &filter )
66
+ format( to ).add_filter( from, &filter )
65
67
  end
66
68
 
67
- def filter( ext )
68
- @filters[ ext.to_s ]
69
+ def format( name = nil )
70
+ name ||= default_format
71
+
72
+ @formats[name.to_s] ||= Rayo::Config::Format.new name
69
73
  end
70
74
 
71
75
  def domain( host )
72
76
  if @domains.empty?
73
77
  self # No multidomain support
74
78
  else
75
- @domains.find { |domain| domain.matches? host }
79
+ @domains.find { |domain| domain.matches? host } || @domains.find { |domain| domain.name == @default_domain }
76
80
  end
77
81
  end
78
82
 
79
83
  end
80
84
 
81
85
  require File.join(File.dirname(__FILE__), 'config/domain.rb')
86
+ require File.join(File.dirname(__FILE__), 'config/format.rb')
@@ -4,7 +4,10 @@ class Rayo::Config::Domain
4
4
 
5
5
  extend Forwardable
6
6
 
7
- def_delegators :@parent, :create_tagger, :languages, :page_exts, :renderable_exts, :filter
7
+ def_delegators :@parent, :create_tagger, :languages, :page_exts, :format, :default_format
8
+
9
+ attr_reader :name
10
+ attr_writer :cache_dir
8
11
 
9
12
  def initialize( parent, name, exp )
10
13
  @parent = parent
@@ -24,4 +27,9 @@ class Rayo::Config::Domain
24
27
  end
25
28
  end
26
29
 
30
+ # Get cache directory for this domain. If it was not set explicitly it'll be global cache_dir with domain name appended
31
+ def cache_dir
32
+ @cache_dir || @parent.cache_dir && File.join( @parent.cache_dir, @name )
33
+ end
34
+
27
35
  end
@@ -0,0 +1,25 @@
1
+ class Rayo::Config::Format
2
+
3
+ attr_reader :name
4
+ attr_reader :renderable_exts
5
+
6
+ def initialize( name )
7
+ @name = name.to_s
8
+ @filters = { @name => lambda{|source| source} }
9
+ @renderable_exts = [ ".#{name}" ]
10
+ end
11
+
12
+ # Add filter
13
+ #
14
+ # @param [String,Symbol] renderable file extension
15
+ # @param [Proc] filter proc which accepts source and return it in processed form
16
+ def add_filter( from, &filter )
17
+ @filters[from.to_s] = filter
18
+ @renderable_exts << ".#{from}" unless @renderable_exts.include? ".#{from}"
19
+ end
20
+
21
+ def filter( from )
22
+ @filters[from.to_s]
23
+ end
24
+
25
+ end
@@ -13,6 +13,9 @@ class Rayo::Models::Page
13
13
  @parent = parent
14
14
  @lang = lang
15
15
  @path = path
16
+
17
+ @parts = {}
18
+ @layouts = {}
16
19
  end
17
20
 
18
21
  def descendant( relative_path )
@@ -27,6 +30,18 @@ class Rayo::Models::Page
27
30
  @children ||= @storage.find_pages( directories, @lang ).map{|name| child( name ) }
28
31
  end
29
32
 
33
+ def relative( url )
34
+ path = url.split '/'
35
+
36
+ if path.empty?
37
+ @storage.root_page( @lang )
38
+ elsif path.first && path.first.empty?
39
+ @storage.root_page( @lang ).descendant( path[1..-1] )
40
+ else
41
+ descendant( path )
42
+ end
43
+ end
44
+
30
45
  def child( slug )
31
46
  @children_cache ||= {}
32
47
  return @children_cache[ slug ] if @children_cache.include? slug
@@ -45,17 +60,25 @@ class Rayo::Models::Page
45
60
  @file ||= @storage.find_page_file( @parent.directories, @lang, @path.last )
46
61
  end
47
62
 
48
- def parts
49
- @parts ||= file ? @storage.find_page_parts( file, @lang ) : {}
63
+ # Get page parts for given format
64
+ # @param [String,Symbol] part format
65
+ # @return [List<Rayo::Models::Renderable>] part list
66
+ def parts( format )
67
+ @parts[format.to_s] ||= file ? @storage.find_page_parts( file, @lang, format.to_s ) : {}
50
68
  end
51
69
 
52
- def find_part( part_name, inherit = false )
70
+ # Find page part for given format
71
+ # @param [String,Symbol] part format
72
+ # @param [String,Symbol] part name
73
+ # @param [Boolean] search in super classes
74
+ # @return [List<Rayo::Models::Renderable>] part list
75
+ def find_part( format, part_name, inherit = false )
53
76
  page = self
54
- part = self.parts[part_name]
77
+ part = self.parts(format)[part_name]
55
78
 
56
79
  while inherit && !part && page.parent do
57
80
  page = page.parent
58
- part = page.parts[part_name]
81
+ part = page.parts(format)[part_name]
59
82
  end
60
83
 
61
84
  part
@@ -84,8 +107,8 @@ class Rayo::Models::Page
84
107
  @context
85
108
  end
86
109
 
87
- def layout
88
- @layout ||= @storage.layout( @lang, context['layout'] )
110
+ def layout(format)
111
+ @layouts[format.to_s] ||= @storage.layout( @lang, context['layout'], format )
89
112
  end
90
113
 
91
114
  def []( key )
@@ -94,8 +117,12 @@ class Rayo::Models::Page
94
117
  context[ key ] || params[ key ]
95
118
  end
96
119
 
97
- def render
98
- layout.render( parser )
120
+ def render( format )
121
+ if l = layout( format )
122
+ l.render( parser )
123
+ else
124
+ "Layout: '#{context['layout']}'' not found with format '#{format}'"
125
+ end
99
126
  end
100
127
 
101
128
  def parser
@@ -105,7 +132,13 @@ class Rayo::Models::Page
105
132
  private
106
133
 
107
134
  def load_context( filename )
108
- YAML::load( Erubis::Eruby.new( @storage.load( filename ) ).result( params ) )
135
+ cont = @storage.load( filename )
136
+
137
+ if cont && !cont.strip.empty?
138
+ YAML::load( Erubis::Eruby.new( cont ).result( params ) )
139
+ else
140
+ {}
141
+ end
109
142
  end
110
143
 
111
144
  end
@@ -2,11 +2,13 @@ class Rayo::Models::Renderable
2
2
 
3
3
  attr_reader :storage
4
4
  attr_reader :file
5
+ attr_reader :format
5
6
  attr_reader :filter
6
7
 
7
- def initialize( storage, file, filter )
8
+ def initialize( storage, file, format, filter )
8
9
  @storage = storage
9
10
  @file = file
11
+ @format = format
10
12
  @filter = filter
11
13
  end
12
14
 
@@ -15,7 +17,9 @@ class Rayo::Models::Renderable
15
17
  end
16
18
 
17
19
  def render( parser )
18
- filter.call( parser.parse( source ) )
20
+ parser.context.with_format @format do
21
+ filter.call( parser.parse( source ) )
22
+ end
19
23
  end
20
24
 
21
25
  end
@@ -13,12 +13,12 @@ class Rayo::Storage
13
13
  @snippets = {}
14
14
  end
15
15
 
16
- def snippet( lang, name )
17
- @snippets["#{lang}|#{name}"] ||= find_renderable( :snippets, lang, name.to_s ) || raise( "Snippet '#{name}' not found" )
16
+ def snippet( lang, name, format )
17
+ @snippets["#{lang}|#{name}|#{format}"] ||= find_renderable( :snippets, lang, name.to_s, format )
18
18
  end
19
19
 
20
- def layout( lang, name )
21
- @layouts["#{lang}|#{name}"] ||= find_renderable( :layouts, lang, name.to_s ) || raise( "Layout '#{name}' not found" )
20
+ def layout( lang, name, format )
21
+ @layouts["#{lang}|#{name}|#{format}"] ||= find_renderable( :layouts, lang, name.to_s, format )
22
22
  end
23
23
 
24
24
  # Retrieves root page for specific language
@@ -47,14 +47,14 @@ class Rayo::Storage
47
47
  Rayo::Models::StatusPage.new( self, root_page( lang ), path, status )
48
48
  end
49
49
 
50
- def find_renderable( type, lang, name )
51
- if file = find_file( [config.directory( type )], lang, name, config.renderable_exts )
52
- renderable( file, File.extname( file ) )
50
+ def find_renderable( type, lang, name, format )
51
+ if file = find_file( [config.directory( type )], lang, name, config.format( format ).renderable_exts )
52
+ renderable( file, format, File.extname( file ) )
53
53
  end
54
54
  end
55
55
 
56
56
  def find_page_file( dirs, lang, slug )
57
- find_file( dirs, lang, slug, config.page_exts )
57
+ find_file( dirs, lang, slug, config.page_exts, true )
58
58
  end
59
59
 
60
60
  def find_page_dirs( dirs, lang, slug )
@@ -68,28 +68,28 @@ class Rayo::Storage
68
68
  elem_name = elems[0]
69
69
  elem_lang = elems[1]
70
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)
71
+ res << elem_name unless ['%','_'].include?( elem_name[0..0] ) || (dirs == ([ @config.directory :pages ]) && (elem_name == 'index')) || (elem_lang && elem_lang != lang)
72
72
  end
73
73
  res.uniq
74
74
  end
75
75
 
76
- def find_page_parts( page_file, lang )
76
+ def find_page_parts( page_file, lang, format )
77
77
  parts = {}
78
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|
79
+ glob_files File.dirname( page_file ), lang, page_file_base + '*', config.format( format ).renderable_exts do |file,base,ext|
80
80
  elems = base.split('.')
81
81
 
82
82
  if elems.shift == page_file_base # Remove base (slug or variable)
83
83
  if elems.size == 0 # There are no part name and language
84
- parts[ 'body' ] ||= renderable( file, ext )
84
+ parts[ 'body' ] ||= renderable( file, format, ext )
85
85
  elsif elems.size == 1 # There are no language or no part name
86
86
  if elems[0] == lang
87
- parts[ 'body' ] ||= renderable( file, ext )
87
+ parts[ 'body' ] ||= renderable( file, format, ext )
88
88
  else
89
- parts[ elems[0] ] ||= renderable( file, ext )
89
+ parts[ elems[0] ] ||= renderable( file, format, ext )
90
90
  end
91
91
  else
92
- parts[ elems[0] ] ||= renderable( file, ext ) if elems[1] == lang
92
+ parts[ elems[0] ] ||= renderable( file, format, ext ) if elems[1] == lang
93
93
  end
94
94
  end
95
95
  end
@@ -102,13 +102,13 @@ class Rayo::Storage
102
102
 
103
103
  private
104
104
 
105
- def renderable( file, ext )
106
- Rayo::Models::Renderable.new( self, file, config.filter( ext ) || raise( "Filter for '#{ext} not found" ) )
105
+ def renderable( file, format, ext )
106
+ Rayo::Models::Renderable.new( self, file, format, config.format( format ).filter( ext[1..-1] ) || raise( "Filter for '#{ext} not found" ) )
107
107
  end
108
108
 
109
109
  # Find first file with given name (or variable) and extension from given set
110
- def find_file( dirs, lang, name, exts )
111
- glob_files( dirs, lang, name, exts ) { |file,base,ext| return file }
110
+ def find_file( dirs, lang, name, exts, hidden = false )
111
+ glob_files( dirs, lang, name, exts, hidden ) { |file,base,ext| return file }
112
112
  nil
113
113
  end
114
114
 
@@ -119,11 +119,17 @@ class Rayo::Storage
119
119
  results
120
120
  end
121
121
 
122
- def glob_files( dirs, lang, name, exts, &block )
122
+ def glob_files( dirs, lang, name, exts, hidden = false, &block )
123
123
  lang_prefix = "." + lang
124
124
 
125
125
  glob dirs, name + lang_prefix, //, exts, &block # Search with given name and language
126
126
  glob dirs, name, //, exts, &block # Search with given name without language
127
+
128
+ if hidden
129
+ glob dirs, '_' + name + lang_prefix, //, exts, &block # Search hidden with given name and language
130
+ glob dirs, '_' + name, //, exts, &block # Search hidden with given name without language
131
+ end
132
+
127
133
  glob dirs, '%*' + lang_prefix, /^%.+\.#{lang}$/, exts, &block # Search with variable and language
128
134
  glob dirs, '%*', /^%.+$/, exts, &block # Search with variable without language
129
135
  end
@@ -9,6 +9,7 @@ class Rayo::TagContext < Radius::Context
9
9
 
10
10
  globals.page = @page
11
11
  globals.storage = @page.storage
12
+ globals.config = @page.storage.config
12
13
 
13
14
  tagger = @page.storage.config.create_tagger
14
15
  tagger.methods.each do |name|
@@ -32,4 +33,15 @@ class Rayo::TagContext < Radius::Context
32
33
  "<strong class=\"error\">#{text}</strong>"
33
34
  end
34
35
 
36
+ def with_format( format )
37
+ old_format = globals.format
38
+ globals.format = format
39
+
40
+ result = yield
41
+
42
+ globals.format = old_format
43
+
44
+ result
45
+ end
46
+
35
47
  end
@@ -24,7 +24,7 @@ module Rayo::Tags::ContentTags
24
24
  tag.globals.layout_stack ||= [] # Prepare layout stack
25
25
  tag.globals.content_stack ||= [] # Prepare content stack
26
26
 
27
- if layout = tag.globals.storage.layout( tag.globals.page.lang, name )
27
+ if layout = tag.globals.storage.layout( tag.globals.page.lang, name, tag.globals.format )
28
28
  tag.globals.layout_stack << name # Track this layout on the stack
29
29
  tag.globals.content_stack << tag.expand # Save contents of inside_layout for later insertion
30
30
 
@@ -39,7 +39,7 @@ module Rayo::Tags::ContentTags
39
39
 
40
40
  tag 'snippet' do |tag|
41
41
  snippet_name = tag.attr['name']
42
- snippet = tag.globals.storage.snippet( tag.globals.page.lang, snippet_name )
42
+ snippet = tag.globals.storage.snippet( tag.globals.page.lang, snippet_name, tag.attr['format'] || tag.globals.format )
43
43
 
44
44
  if snippet
45
45
  snippet.render( tag.globals.page.parser )
@@ -48,10 +48,21 @@ module Rayo::Tags::ContentTags
48
48
  end
49
49
  end
50
50
 
51
+ tag 'with' do |tag|
52
+ old_format = tag.globals.format
53
+ tag.globals.format = tag.attr['format']
54
+
55
+ result = tag.expand
56
+
57
+ tag.globals.format = old_format
58
+
59
+ result
60
+ end
61
+
51
62
  private
52
63
 
53
64
  def find_part( tag )
54
- tag.locals.page.find_part( tag.attr['part'] || 'body', tag.attr['inherit'] == 'true' )
65
+ tag.locals.page.find_part( tag.attr['format'] || tag.globals.format, tag.attr['part'] || 'body', tag.attr['inherit'] == 'true' )
55
66
  end
56
67
 
57
68
  end
@@ -3,19 +3,7 @@ module Rayo::Tags::NavigationTags
3
3
  include Rayo::Taggable
4
4
 
5
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( tag.globals.page.lang )
12
- else
13
- page = tag.locals.page
14
- end
15
-
16
- page = page.descendant( path )
17
-
18
- if page
6
+ if page = tag.locals.page.relative( tag.attr['url'] )
19
7
  tag.locals.page = page
20
8
  tag.expand
21
9
  else
@@ -24,40 +12,101 @@ module Rayo::Tags::NavigationTags
24
12
  end
25
13
 
26
14
  tag 'children' do |tag|
27
- tag.locals.children = tag.locals.page.children
15
+ children = tag.locals.page.children
16
+
17
+ [ tag.attr['level'].to_i - 1, 0 ].max.times do
18
+ children = children.map{ |p| p.children }.flatten
19
+ end
20
+
21
+ tag.locals.children = children
28
22
  tag.expand
29
23
  end
30
24
 
31
25
  tag 'children:count' do |tag|
32
- tag.locals.children.size
26
+ prepare_children( tag ).size
33
27
  end
34
28
 
35
29
  tag 'children:first' do |tag|
36
- if first = tag.locals.children.first
30
+ if first = prepare_children( tag ).first
37
31
  tag.locals.page = first
38
32
  tag.expand
39
33
  end
40
34
  end
41
35
 
42
36
  tag 'children:last' do |tag|
43
- if first = tag.locals.children.last
44
- tag.locals.page = first
37
+ if last = prepare_children( tag ).last
38
+ tag.locals.page = last
45
39
  tag.expand
46
40
  end
47
41
  end
48
42
 
49
43
  tag 'children:each' do |tag|
50
- result = ''
44
+ result = ""
45
+ children = prepare_children( tag )
51
46
 
52
- children = tag.locals.children
53
47
  children.each_with_index do |item, i|
54
48
  tag.locals.child = item
55
49
  tag.locals.page = item
56
50
  tag.locals.first_child = i == 0
57
- tag.locals.last_child = i == children.length - 1
51
+ tag.locals.last_child = i == children.size - 1
52
+
53
+ result << tag.expand
54
+ end
55
+ result
56
+ end
57
+
58
+ tag 'related' do |tag|
59
+ tag.locals.relation = tag.locals.page['relations'][tag.attr['rel']]
60
+ tag.expand
61
+ end
62
+
63
+ tag 'related:count' do |tag|
64
+ tag.locals.relation.size
65
+ end
66
+
67
+ tag 'related:first' do |tag|
68
+ if first = tag.locals.relation.first
69
+ tag.locals.page = tag.locals.page.relative first
70
+ tag.expand
71
+ end
72
+ end
73
+
74
+ tag 'related:last' do |tag|
75
+ if last = tag.locals.relation.last
76
+ tag.locals.page = tag.locals.page.relative last
77
+ tag.expand
78
+ end
79
+ end
80
+
81
+ tag 'related:each' do |tag|
82
+ result = ''
83
+
84
+ relation = tag.locals.relation
85
+ relation.each_with_index do |item, i|
86
+ tag.locals.page = tag.locals.page.relative item
87
+ tag.locals.first_related = i == 0
88
+ tag.locals.last_related = i == relation.size - 1
89
+
58
90
  result << tag.expand
59
91
  end
60
92
  result
61
93
  end
62
94
 
95
+ private
96
+
97
+ def prepare_children( tag )
98
+ children = tag.locals.children
99
+
100
+ if by = tag.attr['by']
101
+ if tag.attr['order'] == 'desc'
102
+ children = children.sort {|a,b| b[by] <=> a[by] }
103
+ else
104
+ children = children.sort {|a,b| a[by] <=> b[by] }
105
+ end
106
+ end
107
+
108
+ children = children[0..(tag.attr['limit'].to_i - 1)] if tag.attr['limit']
109
+ children
110
+ end
111
+
63
112
  end
@@ -10,6 +10,10 @@ module Rayo::Tags::PropertyTags
10
10
  tag.locals.page.context['description']
11
11
  end
12
12
 
13
+ tag 'value' do |tag|
14
+ tag.locals.page[tag.attr['name']]
15
+ end
16
+
13
17
  tag 'path' do |tag|
14
18
  basepath = tag.globals.page.path
15
19
  path = tag.locals.page.path
@@ -24,10 +28,16 @@ module Rayo::Tags::PropertyTags
24
28
  i = i + 1
25
29
  end
26
30
 
27
- '../' * (basepath.size - i) + path[i..-1].join('/')
31
+ './' + ('../' * (basepath.size - i)) + path[i..-1].join('/')
28
32
  end
29
33
  end
30
34
 
35
+ tag 'url' do |tag|
36
+ u = "/#{tag.locals.page.lang}/#{tag.locals.page.path.join('/')}"
37
+ u << ".#{tag.globals.format}" if tag.globals.format != tag.globals.config.default_format
38
+ u
39
+ end
40
+
31
41
  tag 'link' do |tag|
32
42
  "<a href=\"#{send 'tag:path', tag}\">#{tag.single? ? send( 'tag:title', tag ) : tag.expand}</a>"
33
43
  end
@@ -41,11 +51,25 @@ module Rayo::Tags::PropertyTags
41
51
  end
42
52
 
43
53
  tag 'date' do |tag|
44
- Time.now.strftime(tag.attr['format'] || '%A, %B %d, %Y')
54
+ date_for( tag ).strftime( tag.attr['format'] || '%A, %B %d, %Y' )
55
+ end
56
+
57
+ tag 'rfc1123_date' do |tag|
58
+ CGI.rfc1123_date( date_for( tag ) )
45
59
  end
46
60
 
47
61
  private
48
62
 
63
+ def date_for( tag )
64
+ if tag.attr['for'] == 'now'
65
+ Time.now
66
+ elsif tag.attr['for']
67
+ tag.locals.page[tag.attr['for']]
68
+ else
69
+ tag.locals.page['published_at']
70
+ end
71
+ end
72
+
49
73
  def if_url( tag )
50
74
  tag.locals.page.path =~ Regexp.new( tag.attr['matches'] )
51
75
  end
@@ -1,7 +1,7 @@
1
1
  module Rayo
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 2
4
+ MINOR = 3
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: 23
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.3.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-11-02 00:00:00 +03:00
18
+ date: 2010-11-20 00:00:00 +03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -54,11 +54,12 @@ dependencies:
54
54
  requirements:
55
55
  - - ">="
56
56
  - !ruby/object:Gem::Version
57
- hash: 15
57
+ hash: 19
58
58
  segments:
59
59
  - 1
60
+ - 1
60
61
  - 0
61
- version: "1.0"
62
+ version: 1.1.0
62
63
  type: :runtime
63
64
  version_requirements: *id003
64
65
  - !ruby/object:Gem::Dependency
@@ -102,6 +103,7 @@ extra_rdoc_files:
102
103
  - MIT-LICENSE
103
104
  files:
104
105
  - lib/rayo.rb
106
+ - lib/rayo/config/format.rb
105
107
  - lib/rayo/config/domain.rb
106
108
  - lib/rayo/storage.rb
107
109
  - lib/rayo/application.rb