broadway 0.0.3.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.markdown +155 -0
- data/lib/broadway.rb +64 -4
- data/lib/broadway/{core_ext.rb → ext.rb} +40 -6
- data/lib/broadway/filters/erb.rb +10 -0
- data/lib/broadway/filters/haml.rb +10 -0
- data/lib/broadway/filters/liquid.rb +14 -0
- data/lib/broadway/filters/markdown.rb +10 -0
- data/lib/broadway/filters/textile.rb +11 -0
- data/lib/broadway/migrators/blogger.rb +7 -0
- data/lib/broadway/migrators/wordpress.rb +7 -0
- data/lib/broadway/mixins/assetable.rb +48 -0
- data/lib/broadway/mixins/configurable.rb +32 -0
- data/lib/broadway/mixins/convertible.rb +25 -0
- data/lib/broadway/mixins/hierarchical.rb +61 -0
- data/lib/broadway/mixins/layoutable.rb +18 -0
- data/lib/broadway/mixins/pageable.rb +5 -0
- data/lib/broadway/mixins/processable.rb +44 -0
- data/lib/broadway/mixins/publishable.rb +36 -0
- data/lib/broadway/mixins/readable.rb +73 -0
- data/lib/broadway/mixins/resourceful.rb +27 -0
- data/lib/broadway/mixins/sluggable.rb +36 -0
- data/lib/broadway/mixins/sortable.rb +24 -0
- data/lib/broadway/mixins/taggable.rb +46 -0
- data/lib/broadway/mixins/themeable.rb +39 -0
- data/lib/broadway/processors/link.rb +45 -0
- data/lib/broadway/processors/post.rb +117 -0
- data/lib/broadway/processors/site.rb +77 -0
- data/lib/broadway/processors/tree.rb +121 -0
- data/lib/broadway/resources/asset.rb +28 -0
- data/lib/broadway/resources/configuration.rb +114 -0
- data/lib/broadway/resources/file.rb +88 -0
- data/lib/broadway/resources/layout.rb +28 -0
- data/lib/broadway/resources/link.rb +16 -0
- data/lib/broadway/resources/post.rb +63 -0
- data/lib/broadway/resources/site.rb +164 -0
- data/lib/broadway/resources/slug.rb +69 -0
- data/lib/broadway/sinatra/app.rb +21 -0
- data/lib/broadway/sinatra/helpers/collection_helper.rb +2 -1
- data/lib/broadway/sinatra/helpers/partial_helper.rb +5 -5
- data/lib/broadway/sinatra/helpers/text_helper.rb +5 -11
- data/lib/broadway/sinatra/processor.rb +84 -0
- data/lib/broadway/tasks.rb +1 -0
- data/lib/broadway/tasks/default.rake +85 -0
- metadata +46 -41
- data/README.textile +0 -306
- data/Rakefile +0 -85
- data/lib/broadway/api.rb +0 -51
- data/lib/broadway/asset.rb +0 -17
- data/lib/broadway/base.rb +0 -120
- data/lib/broadway/convertible.rb +0 -91
- data/lib/broadway/page.rb +0 -71
- data/lib/broadway/post.rb +0 -112
- data/lib/broadway/rails.rb +0 -3
- data/lib/broadway/resource.rb +0 -128
- data/lib/broadway/runner.rb +0 -62
- data/lib/broadway/sinatra.rb +0 -5
- data/lib/broadway/sinatra/base.rb +0 -90
- data/lib/broadway/sinatra/helpers.rb +0 -7
- data/lib/broadway/site.rb +0 -421
- data/lib/broadway/static_file.rb +0 -32
@@ -0,0 +1,45 @@
|
|
1
|
+
module Broadway
|
2
|
+
module Definition
|
3
|
+
class Link
|
4
|
+
attr_accessor :site, :context, :records
|
5
|
+
|
6
|
+
def initialize(site, context, &block)
|
7
|
+
self.site = site
|
8
|
+
self.context = context
|
9
|
+
self.records = []
|
10
|
+
instance_eval(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def link(*args, &block)
|
14
|
+
options = args.extract_options!
|
15
|
+
href = args.shift
|
16
|
+
title = args.shift || options[:title] || href.split("/").last.titleize
|
17
|
+
record = Broadway::Link.new(site, :href => href, :title => title, :categories => [context.to_s])
|
18
|
+
self.records << record
|
19
|
+
site.links << record
|
20
|
+
if block_given?
|
21
|
+
record.children = Broadway::Definition::Link.new(site, context, &block).records
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Processor
|
28
|
+
class Link
|
29
|
+
class << self
|
30
|
+
def tree!(site, name, &block)
|
31
|
+
Broadway::Definition::Link.new(site, name, &block).records
|
32
|
+
end
|
33
|
+
alias menu! tree!
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(link, site, options = {})
|
37
|
+
link.site = site
|
38
|
+
link.href = options[:href]
|
39
|
+
link.title = options[:title]
|
40
|
+
link.categories = options[:categories]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Broadway
|
2
|
+
module Processor
|
3
|
+
class Post
|
4
|
+
class << self
|
5
|
+
|
6
|
+
# RULES:
|
7
|
+
# Pages are only index.textile files, or static .html files (TODO)
|
8
|
+
# Posts are leaf nodes (name.textile files)
|
9
|
+
# Categories are the directoryectory name split plus anything extra defined
|
10
|
+
# Pages and Posts can also be obtained via index.xml files
|
11
|
+
def build(parent, site)
|
12
|
+
result = Dir.entries(parent).collect do |path|
|
13
|
+
# removes junk, leaves us with .xml, .textile, .html
|
14
|
+
next if skip?(path)
|
15
|
+
name = File.file_name(path)
|
16
|
+
next if name == "index"
|
17
|
+
ext = File.file_ext(path)
|
18
|
+
file = ::File.join(parent, path)
|
19
|
+
if %w(textile markdown html).include?(ext)
|
20
|
+
::Broadway::Post.new(site, :file => file)
|
21
|
+
elsif page = Post.page_exists?(file)
|
22
|
+
::Broadway::Post.new(site, :file => page)
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end.compact
|
27
|
+
|
28
|
+
result.sort!
|
29
|
+
|
30
|
+
# finally, we have set all the initial variables on the
|
31
|
+
# pages and posts we need, now we can process them to find
|
32
|
+
# the content and generate the urls
|
33
|
+
result.each do |post|
|
34
|
+
site.posts << post
|
35
|
+
post.categories.each { |c| site.categories[c] << post }
|
36
|
+
post.tags.each { |c| site.tags[c] << post }
|
37
|
+
end
|
38
|
+
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def page_exists?(file)
|
43
|
+
%w(markdown textile txt).each do |extension|
|
44
|
+
page = ::File.join(file, "index.#{extension}")
|
45
|
+
return page if ::File.exists?(page)
|
46
|
+
end
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Filter out any files/directoryectories that are hidden or backup files (start
|
51
|
+
# with "." or "#" or end with "~"), or contain site content (start with "_"),
|
52
|
+
# or are excluded in the site settingsuration, unless they are web server
|
53
|
+
# files such as '.htaccess'
|
54
|
+
def skip?(path)
|
55
|
+
#return true if ::File.directory?(path)
|
56
|
+
file = ::File.basename(path)
|
57
|
+
return true if ['.htaccess'].include?(file)
|
58
|
+
['.', '_', '#'].include?(file[0..0]) || file[-1..-1] == '~'# || self.exclude.include?(file)
|
59
|
+
end
|
60
|
+
|
61
|
+
# sort all pages and posts
|
62
|
+
# determined by index.xml file (and later, for dates)
|
63
|
+
def sort!
|
64
|
+
sort_content(:post, site.posts)
|
65
|
+
end
|
66
|
+
|
67
|
+
def sort_content(type, contents)
|
68
|
+
method = type.to_s.pluralize
|
69
|
+
unnumbered = site.send(method).select { |content| content.position.nil? }
|
70
|
+
start_index = site.send(method).length - unnumbered.length
|
71
|
+
unnumbered.each_with_index { |content, index| content.position = start_index + index }
|
72
|
+
site.send(method).sort!
|
73
|
+
site.send(method)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Constructs a hash map of Posts indexed by the specified Post attribute
|
77
|
+
#
|
78
|
+
# Returns {post_attr => [<Post>]}
|
79
|
+
def post_attr_hash(post_attr)
|
80
|
+
# Build a hash map based on the specified post attribute ( post attr => array of posts )
|
81
|
+
# then sort each array in reverse order
|
82
|
+
hash = Hash.new { |hash, key| hash[key] = Array.new }
|
83
|
+
self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
|
84
|
+
hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
|
85
|
+
return hash
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(post, site, options = {})
|
90
|
+
post.site = site
|
91
|
+
post.file = Broadway::File.new(site, options)
|
92
|
+
post.kind = "page" if post.file.slug == "index"
|
93
|
+
post.slug = Broadway::Slug.new(post.file.slug, post)
|
94
|
+
post.data = post.file.header
|
95
|
+
post.title = post.data["title"].blank? ? post.slug.titleize : post.data["title"]
|
96
|
+
if post.title.downcase == "index"
|
97
|
+
post.title = ::File.basename(post.file.directory).titleize
|
98
|
+
end
|
99
|
+
unless options[:recursive] == false
|
100
|
+
post.children = children(post.file.directory, site) if post.kind == "page"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def children(path, site)
|
105
|
+
Post.build(path, site) if path
|
106
|
+
end
|
107
|
+
|
108
|
+
def read(attribute = nil)
|
109
|
+
post.file.read(attribute)
|
110
|
+
end
|
111
|
+
|
112
|
+
def write(to)
|
113
|
+
post.file.write(to)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Broadway
|
2
|
+
module Processor
|
3
|
+
class Site
|
4
|
+
attr_accessor :site, :source, :destination, :lsi, :pygments, :exclude, :lists, :configuration
|
5
|
+
|
6
|
+
def initialize(site, settings)
|
7
|
+
self.site = Broadway.site = site
|
8
|
+
|
9
|
+
site.configuration = Broadway::Configuration.new(site, settings)
|
10
|
+
|
11
|
+
self.lists = []
|
12
|
+
self.source = settings[:source]
|
13
|
+
self.lsi = settings[:lsi]
|
14
|
+
self.pygments = settings[:pygments]
|
15
|
+
self.exclude = settings[:exclude] || []
|
16
|
+
|
17
|
+
reset
|
18
|
+
end
|
19
|
+
|
20
|
+
# first
|
21
|
+
def reset
|
22
|
+
site.posts = []
|
23
|
+
site.assets = []
|
24
|
+
site.links = []
|
25
|
+
site.files = []
|
26
|
+
site.categories = Hash.new { |hash, key| hash[key] = [] }
|
27
|
+
site.tags = Hash.new { |hash, key| hash[key] = [] }
|
28
|
+
end
|
29
|
+
|
30
|
+
def destination
|
31
|
+
settings[:destination]
|
32
|
+
end
|
33
|
+
|
34
|
+
def settings
|
35
|
+
site.settings
|
36
|
+
end
|
37
|
+
|
38
|
+
def process
|
39
|
+
build
|
40
|
+
generate
|
41
|
+
end
|
42
|
+
|
43
|
+
def build
|
44
|
+
self.reset
|
45
|
+
self.read
|
46
|
+
site
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate(&block)
|
50
|
+
raise "pass a block to `generate`" unless block_given?
|
51
|
+
raise "define a destination for the site to be written to" if destination.blank?
|
52
|
+
Broadway::Sinatra::Processor.new(site).run(&block) if defined?(::Sinatra)
|
53
|
+
site
|
54
|
+
end
|
55
|
+
|
56
|
+
def read
|
57
|
+
roots
|
58
|
+
links
|
59
|
+
end
|
60
|
+
|
61
|
+
def roots
|
62
|
+
site.roots = Broadway::Processor::Post.build(source, site) if source
|
63
|
+
end
|
64
|
+
|
65
|
+
def links
|
66
|
+
#@links ||= Broadway::Processor::Link.build(source, site)
|
67
|
+
@links
|
68
|
+
end
|
69
|
+
|
70
|
+
def write
|
71
|
+
site.files.each do |file|
|
72
|
+
file.write(self.destination)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# redefine the hierarchy of posts, or order them
|
2
|
+
# DSL for defining initial page hierarchy
|
3
|
+
module Broadway
|
4
|
+
module Processor
|
5
|
+
class Tree
|
6
|
+
attr_accessor :root
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# lists are xml files we've collect, but we want to make sure we've
|
11
|
+
# created all the posts we need to beforehand
|
12
|
+
# this also sorts everything
|
13
|
+
def build(path)
|
14
|
+
list = ::File.join(source, "index.xml")
|
15
|
+
lists << list if ::File.exists?(list)
|
16
|
+
unless lists.blank?
|
17
|
+
require 'nokogiri'
|
18
|
+
lists.each do |path|
|
19
|
+
new_tree(path)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_tree(path)
|
25
|
+
site.tree.concat parse_tree(Nokogiri::XML(IO.read(path)).root)
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_tree(parent)
|
29
|
+
result = []
|
30
|
+
return result if parent.nil? || parent.children.nil? || parent.children.empty?
|
31
|
+
parent.children.each_with_index do |child, index|
|
32
|
+
next unless child.elem?
|
33
|
+
post = post_from_xml(child)
|
34
|
+
next unless post
|
35
|
+
if post.respond_to?(:children)
|
36
|
+
post.children.concat parse_tree(child)
|
37
|
+
end
|
38
|
+
post.position = index
|
39
|
+
result << post
|
40
|
+
end
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
def post_from_xml(post)
|
45
|
+
# post
|
46
|
+
# 1. If a "src" is defined, then we should have already created the post
|
47
|
+
# 2. If no "src", then post is specified inline (or there is no post yet)
|
48
|
+
path = (post["src"] || ::File.join(site.source, post["path"]) || "").gsub(/^\//, "").gsub(/\/$/, "").squeeze("/")
|
49
|
+
post = site.find_post_by_path(path)
|
50
|
+
unless post
|
51
|
+
if ::File.directory?(path)
|
52
|
+
page = Dir.entries(path)[2..-1].detect do |file|
|
53
|
+
::File.basename(file).split(".")[0..-2].join(".").downcase == "index"
|
54
|
+
end
|
55
|
+
if page
|
56
|
+
path = ::File.join(path, page)
|
57
|
+
post = site.find_post_by_path(path)
|
58
|
+
post ||= Broadway::Builder::Post.new_post(path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
return unless post
|
64
|
+
|
65
|
+
%w(title image excerpt menu_title tooltip show_children post layout).each do |key|
|
66
|
+
post.data[key] = post[key] if post.has_attribute?(key)
|
67
|
+
end
|
68
|
+
post
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(*args, &block)
|
73
|
+
self.root = ::Broadway::Definition::Post.new(*args, &block)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module Definition
|
78
|
+
class Base
|
79
|
+
def initialize(*args, &block)
|
80
|
+
options = args.extract_options!
|
81
|
+
|
82
|
+
options.each do |k, v|
|
83
|
+
self.send("#{k}=", v) if self.respond_to?(k)
|
84
|
+
end
|
85
|
+
|
86
|
+
instance_eval(&block) if block_given?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Post
|
91
|
+
attr_accessor :title, :path, :tooltip, :show_children
|
92
|
+
|
93
|
+
def title(value = "")
|
94
|
+
@title = value if value
|
95
|
+
@title
|
96
|
+
end
|
97
|
+
|
98
|
+
def path(value = "")
|
99
|
+
@path = value if value
|
100
|
+
@path
|
101
|
+
end
|
102
|
+
|
103
|
+
def tooltip(value = "")
|
104
|
+
@tooltip = value if value
|
105
|
+
@tooltip
|
106
|
+
end
|
107
|
+
|
108
|
+
def show_children(value = "")
|
109
|
+
@show_children = value if value
|
110
|
+
@show_children
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
class Menu
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Broadway
|
2
|
+
class Asset
|
3
|
+
attr_accessor :site, :title, :tooltip, :thumb, :width, :height, :resource, :content_type, :name, :path
|
4
|
+
|
5
|
+
include Comparable
|
6
|
+
include Broadway::Resourceful
|
7
|
+
include Broadway::Readable
|
8
|
+
include Broadway::Sortable
|
9
|
+
include Broadway::Taggable
|
10
|
+
include Broadway::Configurable
|
11
|
+
|
12
|
+
def initialize(resource, name, options = {})
|
13
|
+
self.resource = resource
|
14
|
+
self.name = name
|
15
|
+
self.data = options.recursively_symbolize_keys!
|
16
|
+
self.path = options.delete(:path)
|
17
|
+
options.each do |k,v|
|
18
|
+
self.send("#{k}=", v) if self.respond_to?(k)
|
19
|
+
end
|
20
|
+
|
21
|
+
self.title ||= Broadway::File.file_name(self.path).titleize
|
22
|
+
end
|
23
|
+
|
24
|
+
def site
|
25
|
+
resource.site
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Broadway
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :settings, :site
|
4
|
+
# Default options. Overriden by values in _settings.yml or command-line opts.
|
5
|
+
# (Strings rather symbols used for compatability with YAML)
|
6
|
+
DEFAULTS = {
|
7
|
+
:auto => false,
|
8
|
+
:server => false,
|
9
|
+
:server_port => 4000,
|
10
|
+
|
11
|
+
:source => ".",
|
12
|
+
:settings => "_config.yml",
|
13
|
+
:destination => "_site",
|
14
|
+
|
15
|
+
:lsi => false,
|
16
|
+
:pygments => false,
|
17
|
+
:markdown => "rdiscount",
|
18
|
+
:permalink => "pretty",
|
19
|
+
:url_type => "relative",
|
20
|
+
:url => "http://localhost:4567",
|
21
|
+
:locales => "locales",
|
22
|
+
:language => "en-US",
|
23
|
+
|
24
|
+
:maruku => {
|
25
|
+
:use_tex => false,
|
26
|
+
:use_divs => false,
|
27
|
+
:png_engine => "blahtex",
|
28
|
+
:png_dir => "images/latex",
|
29
|
+
:png_url => "/images/latex"
|
30
|
+
},
|
31
|
+
|
32
|
+
:layouts => "_layouts",
|
33
|
+
:posts_include => [".textile", ".markdown"],
|
34
|
+
:theme_path => "themes",
|
35
|
+
:theme_versions => ["main"]
|
36
|
+
} unless defined?(Broadway::Configuration::DEFAULTS)
|
37
|
+
|
38
|
+
|
39
|
+
def initialize(site, overrides)
|
40
|
+
self.site = site
|
41
|
+
self.settings = merge(overrides)
|
42
|
+
end
|
43
|
+
|
44
|
+
def merge(overrides)
|
45
|
+
DEFAULTS.deep_merge(read(overrides)).merge(overrides)
|
46
|
+
end
|
47
|
+
|
48
|
+
def get(path)
|
49
|
+
result = settings
|
50
|
+
path.to_s.split(".").each { |node| result = result[node.to_sym] if result }
|
51
|
+
result.nil? ? "" : result
|
52
|
+
end
|
53
|
+
|
54
|
+
def read(overrides)
|
55
|
+
overrides = overrides.recursively_symbolize_keys!
|
56
|
+
# _settings.yml may override default source location, but until
|
57
|
+
# then, we need to know where to look for _settings.yml
|
58
|
+
source = overrides[:source] || DEFAULTS[:source]
|
59
|
+
settings_file = overrides[:settings] || DEFAULTS[:settings]
|
60
|
+
settings_file = ::File.join(source, settings_file) unless ::File.exists?(settings_file)
|
61
|
+
# Get configuration from <source>/_config.yml
|
62
|
+
begin
|
63
|
+
settings = YAML.load_file(settings_file).recursively_symbolize_keys!
|
64
|
+
raise "Invalid configuration - #{settings_file}" if !settings.is_a?(Hash)
|
65
|
+
#$stdout.puts "Configuration from #{settings_file}"
|
66
|
+
rescue => err
|
67
|
+
puts err.inspect
|
68
|
+
#$stderr.puts "WARNING: Could not read configuration. Using defaults (and options)."
|
69
|
+
#$stderr.puts "\t" + err.to_s
|
70
|
+
settings = {}
|
71
|
+
end
|
72
|
+
|
73
|
+
settings
|
74
|
+
end
|
75
|
+
|
76
|
+
def merge_extra_settings!(settings)
|
77
|
+
locale = ::File.join(settings[:locales], "#{settings[:language]}.yml")
|
78
|
+
|
79
|
+
settings.merge!(YAML.load_file(locale)) if ::File.exists?(locale)
|
80
|
+
|
81
|
+
settings_dir = ::File.join(settings[:source], "settings")
|
82
|
+
return unless ::File.exists?(settings_dir)
|
83
|
+
Dir.glob("#{settings_dir}/**/*").each do |file|
|
84
|
+
next if ::File.directory?(file)
|
85
|
+
path = file.gsub(/settings\//, "").split(".")[0..-2].join("")
|
86
|
+
ext = ::File.extname(file)
|
87
|
+
if ext =~ /yml/
|
88
|
+
data = YAML.load_file(file)
|
89
|
+
elsif ext =~ /xml/
|
90
|
+
data = parse_children Nokogiri::XML(IO.read(file)).children[0]
|
91
|
+
else
|
92
|
+
data = IO.read(file)
|
93
|
+
end
|
94
|
+
next unless data
|
95
|
+
name = path.split("/").first
|
96
|
+
if path =~ /\//
|
97
|
+
settings[name] ||= {}
|
98
|
+
target = settings[name]
|
99
|
+
name = path.split("/").last
|
100
|
+
else
|
101
|
+
target = settings
|
102
|
+
end
|
103
|
+
if target.has_key?(name)
|
104
|
+
target[name].merge!(data)
|
105
|
+
else
|
106
|
+
target[name] = data
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def setup
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|