amber 0.2.6
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/LICENSE +661 -0
- data/README.md +114 -0
- data/bin/amber +71 -0
- data/lib/amber.rb +76 -0
- data/lib/amber/cli.rb +98 -0
- data/lib/amber/logger.rb +21 -0
- data/lib/amber/menu.rb +141 -0
- data/lib/amber/page_array.rb +61 -0
- data/lib/amber/render/asset.rb +55 -0
- data/lib/amber/render/autolink.rb +78 -0
- data/lib/amber/render/bracketlink.rb +33 -0
- data/lib/amber/render/helpers/haml_helper.rb +54 -0
- data/lib/amber/render/helpers/html_helper.rb +75 -0
- data/lib/amber/render/helpers/language_helper.rb +25 -0
- data/lib/amber/render/helpers/navigation_helper.rb +203 -0
- data/lib/amber/render/layout.rb +66 -0
- data/lib/amber/render/table_of_contents.rb +383 -0
- data/lib/amber/render/template.rb +158 -0
- data/lib/amber/render/view.rb +189 -0
- data/lib/amber/server.rb +113 -0
- data/lib/amber/site.rb +154 -0
- data/lib/amber/site_configuration.rb +132 -0
- data/lib/amber/static_page.rb +151 -0
- data/lib/amber/static_page/filesystem.rb +270 -0
- data/lib/amber/static_page/page_properties.rb +124 -0
- data/lib/amber/static_page/property_set.rb +78 -0
- data/lib/amber/static_page/render.rb +122 -0
- metadata +203 -0
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
Amber
|
2
|
+
===================================
|
3
|
+
|
4
|
+
Amber is a super simple and flexible static website generator, with good support for localization and navigation.
|
5
|
+
|
6
|
+
This is still experimental code.
|
7
|
+
|
8
|
+
Amber has much in common with other static page generators, but has several features that make it unique:
|
9
|
+
|
10
|
+
* I18n: Primary focus is on very good multi-lingual and localization support.
|
11
|
+
* Inheritance: Properties are inherited in the page hierarchy.
|
12
|
+
* TOC: Support for table of contents (including inserting TOC into other pages).
|
13
|
+
* Flexible: Ability to set custom page path aliases, render partials, rich navigation, and so on.
|
14
|
+
|
15
|
+
Installation
|
16
|
+
---------------------------------
|
17
|
+
|
18
|
+
Installing from gem:
|
19
|
+
|
20
|
+
sudo gem install amber
|
21
|
+
|
22
|
+
Installing from source:
|
23
|
+
|
24
|
+
sudo gem install bundler
|
25
|
+
git clone https://github.com/leapcode/amber
|
26
|
+
cd amber
|
27
|
+
bundle install
|
28
|
+
sudo ln -s `pwd`/bin/amber /usr/local/bin
|
29
|
+
|
30
|
+
Directory structure
|
31
|
+
---------------------------------
|
32
|
+
|
33
|
+
A simple website has this structure:
|
34
|
+
|
35
|
+
mysite/
|
36
|
+
amber/
|
37
|
+
config.rb
|
38
|
+
locales/
|
39
|
+
en.yml
|
40
|
+
menu.txt
|
41
|
+
layouts/
|
42
|
+
default.haml
|
43
|
+
pages/
|
44
|
+
page1.en.md
|
45
|
+
page2.en.md
|
46
|
+
public/
|
47
|
+
page1.en.html
|
48
|
+
page1.en.html
|
49
|
+
|
50
|
+
menu.txt
|
51
|
+
---------------------------------
|
52
|
+
|
53
|
+
A page does not show up in the navigation unless it appears in menu.txt.
|
54
|
+
|
55
|
+
The order in menu.txt determines the order in the navigation. For example:
|
56
|
+
|
57
|
+
aaa
|
58
|
+
ccc
|
59
|
+
bbb
|
60
|
+
|
61
|
+
Page hierarchy is represented by two spaces:
|
62
|
+
|
63
|
+
aaa
|
64
|
+
bbb
|
65
|
+
ccc
|
66
|
+
ddd
|
67
|
+
eee
|
68
|
+
|
69
|
+
The items in the menu.txt file must match the names of the pages (the filename with the suffix and locale stripped away).
|
70
|
+
|
71
|
+
Supported syntaxes
|
72
|
+
---------------------------------
|
73
|
+
|
74
|
+
Depending the the file extension, the file with be parsed like so:
|
75
|
+
|
76
|
+
.haml -- HAML
|
77
|
+
.md -- Markdown
|
78
|
+
.markdown -- Markdown
|
79
|
+
.txt -- Textile
|
80
|
+
.textile -- Textile
|
81
|
+
.rst -- ReStructuredText
|
82
|
+
|
83
|
+
Markdown is rendered using RDiscount, Textile is rendered using RedCloth, and RST is rendered using docutils. Markdown is recommended, because it supports table of contents, although the markup is limited.
|
84
|
+
|
85
|
+
There are a couple options to preview your source files without needing to run the web server:
|
86
|
+
|
87
|
+
* Markdown preview for Chrome: https://chrome.google.com/webstore/detail/markdown-preview/jmchmkecamhbiokiopfpnfgbidieafmd
|
88
|
+
* Markdown preview for Sublime: https://github.com/revolunet/sublimetext-markdown-preview
|
89
|
+
* Markdown preview for Firefox: https://addons.mozilla.org/de/firefox/addon/markdown-viewer/ (see https://addons.mozilla.org/en-US/firefox/addon/markdown-viewer/reviews/423328/ for rendering .md file extensions)
|
90
|
+
|
91
|
+
Setting page properties
|
92
|
+
---------------------------------
|
93
|
+
|
94
|
+
HAML files are rendered as templates, but the other lightweight markup files are treated as static files.
|
95
|
+
|
96
|
+
The one exception is that every file can have a "properties header". It looks like this:
|
97
|
+
|
98
|
+
@title = "A fine page"
|
99
|
+
@toc = false
|
100
|
+
|
101
|
+
continue on here with body text.
|
102
|
+
|
103
|
+
The properties start with '@' and are stripped out of the source file before it is rendered. Property header lines are evaluated as ruby. All properties are optional and they are inherited, including `@title`.
|
104
|
+
|
105
|
+
Available properties:
|
106
|
+
|
107
|
+
* `@title` -- The title for the page, appearing as in an H1 on the top of the page and as the HTML title. Also used for navigation title if `@nav_title` is not set. The inline H1 title does not appear unless `@title` is explicitly set for this page (i.e. the inherited value of `@title` does not trigger the automatic H1).
|
108
|
+
* `@nav_title` -- The title for the navigation to this page, as well as the HTML title if @title is not set.
|
109
|
+
* `@toc` -- If set to `false`, don't include a table of contents when rendering the file. This only applies to .rst and .md files.
|
110
|
+
* `@layout` -- Manually set the layout template to use for rendering this page.
|
111
|
+
* `@author` -- The author credit for the page.
|
112
|
+
* `@this.alias` -- Alternate paths for the page to be rendered on. May be an array. The first path will be used when linking.
|
113
|
+
|
114
|
+
To make a property none-inheritable, specify it like so: `@this.layout = 'home'`. For some properties, like `alias`, it does not make sense for the property to be inheritable.
|
data/bin/amber
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if ARGV.include?('--debug')
|
4
|
+
require 'debugger'
|
5
|
+
end
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'amber'
|
9
|
+
rescue LoadError
|
10
|
+
base_dir = File.expand_path('..', File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__))
|
11
|
+
["#{base_dir}/lib"].each do |path|
|
12
|
+
$LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
|
13
|
+
end
|
14
|
+
require 'amber'
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_command_line_arguments
|
18
|
+
@command = nil
|
19
|
+
@command_arg = nil
|
20
|
+
@verbose = false
|
21
|
+
loop do
|
22
|
+
case ARGV[0]
|
23
|
+
when 'init' then @command = ARGV.shift; @command_arg = ARGV.shift
|
24
|
+
when 'build' then @command = ARGV.shift
|
25
|
+
when 'rebuild' then @command = ARGV.shift
|
26
|
+
when 'clean' then @command = 'clear'; ARGV.shift
|
27
|
+
when 'clear' then @command = ARGV.shift
|
28
|
+
when 'server' then @command = ARGV.shift
|
29
|
+
when '--port' then ARGV.shift; @port = ARGV.shift
|
30
|
+
when '--debug' then ARGV.shift
|
31
|
+
when '--help' then usage
|
32
|
+
when 'help' then usage
|
33
|
+
when '-h' then usage
|
34
|
+
when '-v' then @verbose = true; ARGV.shift
|
35
|
+
when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")
|
36
|
+
else break
|
37
|
+
end
|
38
|
+
end
|
39
|
+
usage("No command given") unless @command
|
40
|
+
end
|
41
|
+
|
42
|
+
def usage(msg=nil)
|
43
|
+
$stderr.puts(msg) if msg
|
44
|
+
$stderr.puts
|
45
|
+
$stderr.puts("Usage: #{File.basename($0)} [OPTIONS] COMMAND")
|
46
|
+
$stderr.puts
|
47
|
+
$stderr.puts("COMMAND may be one or more of:
|
48
|
+
init DIRECTORY -- create a new amber site in DIRECTORY
|
49
|
+
build -- render static html pages
|
50
|
+
rebuild -- runs `clear` then `build`
|
51
|
+
server -- runs mini-web server at localhost:8000
|
52
|
+
clear -- erase static html pages
|
53
|
+
clean -- alias for clear
|
54
|
+
help -- this message")
|
55
|
+
$stderr.puts
|
56
|
+
$stderr.puts("OPTIONS may be one or more of:
|
57
|
+
-v -- run in verbose mode
|
58
|
+
--debug -- enable debugger
|
59
|
+
--port PORT -- sets the port for the `server` command (default 8000)")
|
60
|
+
exit(2)
|
61
|
+
end
|
62
|
+
|
63
|
+
def main
|
64
|
+
process_command_line_arguments
|
65
|
+
if @verbose
|
66
|
+
Amber.logger.level = Logger::DEBUG
|
67
|
+
end
|
68
|
+
Amber::CLI.new(Dir.pwd).send(@command, {:port => @port, :verbose => @verbose, :arg => @command_arg})
|
69
|
+
end
|
70
|
+
|
71
|
+
main
|
data/lib/amber.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'i18n'
|
5
|
+
require 'i18n/backend/fallbacks'
|
6
|
+
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
|
7
|
+
|
8
|
+
require 'sass' # sass seems to freak out if require'd after haml.
|
9
|
+
require 'haml' #
|
10
|
+
|
11
|
+
module Amber
|
12
|
+
|
13
|
+
class MissingTemplate < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Languages that might possibly be supported.
|
18
|
+
#
|
19
|
+
# https://en.wikipedia.org/wiki/List_of_languages_by_number_of_native_speakers
|
20
|
+
#
|
21
|
+
POSSIBLE_LANGUAGES = {
|
22
|
+
:zh => ['中文', 'zh', 1, false],
|
23
|
+
:es => ['Español', 'es', 2, false],
|
24
|
+
:en => ['English', 'en', 3, false],
|
25
|
+
:hi => ['Hindi', 'hi', 4, false],
|
26
|
+
:ar => ['العربية', 'ar', 5, true],
|
27
|
+
:pt => ['Português', 'pt', 6, false],
|
28
|
+
:ru => ['Pyccĸий', 'ru', 7, false],
|
29
|
+
:de => ['Deutsch', 'de', 8, false],
|
30
|
+
:fr => ['Français', 'fr', 10, false],
|
31
|
+
:it => ['Italiano', 'it', 11, false],
|
32
|
+
:el => ['Ελληνικά', 'el', 20, false],
|
33
|
+
:ca => ['Català', 'ca', 101, false]
|
34
|
+
}
|
35
|
+
|
36
|
+
# Although everywhere else we use symbols for locales, this array should be strings:
|
37
|
+
POSSIBLE_LANGUAGE_CODES = POSSIBLE_LANGUAGES.keys.map(&:to_s)
|
38
|
+
|
39
|
+
# Possible page suffixes. Only files with these suffixes are treated as pages
|
40
|
+
PAGE_SUFFIXES = %w(haml md markdown text textile rst html)
|
41
|
+
|
42
|
+
def self.logger
|
43
|
+
@logger ||= begin
|
44
|
+
logger = Logger.new(STDOUT)
|
45
|
+
logger.level = Logger::INFO
|
46
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
47
|
+
"#{severity}: #{msg}\n"
|
48
|
+
end
|
49
|
+
logger
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
require 'amber/cli'
|
56
|
+
require 'amber/server'
|
57
|
+
require 'amber/logger'
|
58
|
+
|
59
|
+
require 'amber/menu'
|
60
|
+
require 'amber/site'
|
61
|
+
require 'amber/site_configuration'
|
62
|
+
|
63
|
+
require 'amber/static_page'
|
64
|
+
require 'amber/static_page/filesystem'
|
65
|
+
require 'amber/static_page/render'
|
66
|
+
require 'amber/static_page/property_set'
|
67
|
+
require 'amber/static_page/page_properties'
|
68
|
+
require 'amber/page_array'
|
69
|
+
|
70
|
+
require 'amber/render/layout'
|
71
|
+
require 'amber/render/view'
|
72
|
+
require 'amber/render/template'
|
73
|
+
require 'amber/render/asset'
|
74
|
+
require 'amber/render/autolink'
|
75
|
+
require 'amber/render/bracketlink'
|
76
|
+
require 'amber/render/table_of_contents'
|
data/lib/amber/cli.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Amber
|
4
|
+
class CLI
|
5
|
+
|
6
|
+
def initialize(root, *args)
|
7
|
+
@options = {}
|
8
|
+
@options = args.pop if args.last.is_a?(::Hash)
|
9
|
+
@root = File.expand_path(root)
|
10
|
+
end
|
11
|
+
|
12
|
+
def init(options)
|
13
|
+
new_dir = options[:arg]
|
14
|
+
mkdir(new_dir, nil)
|
15
|
+
mkdir('amber', new_dir)
|
16
|
+
touch('amber/config.rb', new_dir)
|
17
|
+
touch('amber/menu.txt', new_dir)
|
18
|
+
mkdir('amber/layouts', new_dir)
|
19
|
+
mkdir('amber/locales', new_dir)
|
20
|
+
mkdir('public', new_dir)
|
21
|
+
mkdir('pages', new_dir)
|
22
|
+
end
|
23
|
+
|
24
|
+
def build(options)
|
25
|
+
site = Site.new(@root)
|
26
|
+
site.load_pages
|
27
|
+
site.render
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear(options)
|
31
|
+
site = Site.new(@root)
|
32
|
+
site.clear
|
33
|
+
end
|
34
|
+
|
35
|
+
def clean(options)
|
36
|
+
clear(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def rebuild(options)
|
40
|
+
site = Site.new(@root)
|
41
|
+
site.continue_on_error = false
|
42
|
+
site.load_pages
|
43
|
+
gitkeep = File.exists?(File.join(site.dest_dir, '.gitkeep'))
|
44
|
+
temp_render = File.join(File.dirname(site.dest_dir), 'public-tmp')
|
45
|
+
temp_old_pages = File.join(File.dirname(site.dest_dir), 'remove-me')
|
46
|
+
site.with_destination(temp_render) do
|
47
|
+
site.render
|
48
|
+
end
|
49
|
+
FileUtils.mv(site.dest_dir, temp_old_pages)
|
50
|
+
FileUtils.mv(temp_render, site.dest_dir)
|
51
|
+
site.with_destination(temp_old_pages) do
|
52
|
+
site.clear
|
53
|
+
FileUtils.rm_r(temp_old_pages)
|
54
|
+
end
|
55
|
+
if gitkeep
|
56
|
+
FileUtils.touch(File.join(site.dest_dir, '.gitkeep'))
|
57
|
+
end
|
58
|
+
ensure
|
59
|
+
# cleanup if something goes wrong.
|
60
|
+
FileUtils.rm_r(temp_render) if temp_render && File.exists?(temp_render)
|
61
|
+
FileUtils.rm_r(temp_old_pages) if temp_old_pages && File.exists?(temp_old_pages)
|
62
|
+
end
|
63
|
+
|
64
|
+
def server(options)
|
65
|
+
site = Site.new(@root)
|
66
|
+
Amber::Server.start(:port => (options[:port] || 8000), :site => site)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def mkdir(dir, context)
|
72
|
+
if context
|
73
|
+
path = File.join(context, dir)
|
74
|
+
print_path = File.join(File.basename(context), dir)
|
75
|
+
else
|
76
|
+
path = dir
|
77
|
+
print_path = dir
|
78
|
+
end
|
79
|
+
unless Dir.exists?(path)
|
80
|
+
if File.exists?(path)
|
81
|
+
puts "Could not make directory `#{print_path}`. File already exists."
|
82
|
+
exit(1)
|
83
|
+
end
|
84
|
+
FileUtils.mkdir_p(path)
|
85
|
+
puts "* Creating `#{print_path}`"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def touch(file, context)
|
90
|
+
path = File.join(context, file)
|
91
|
+
unless File.exists?(path)
|
92
|
+
FileUtils.touch(path)
|
93
|
+
puts "* Creating `#{File.basename(context)}/#{file}`"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
data/lib/amber/logger.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Amber
|
4
|
+
|
5
|
+
def self.logger
|
6
|
+
@logger ||= begin
|
7
|
+
logger = Logger.new(STDOUT)
|
8
|
+
logger.level = Logger::INFO
|
9
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
10
|
+
"#{severity}: #{msg}\n"
|
11
|
+
end
|
12
|
+
logger
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.log_exception(e)
|
17
|
+
Amber.logger.error(e)
|
18
|
+
Amber.logger.error(e.backtrace.join("\n "))
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/amber/menu.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
#
|
2
|
+
# A navigation menu class
|
3
|
+
#
|
4
|
+
|
5
|
+
module Amber
|
6
|
+
class Menu
|
7
|
+
attr_accessor :parent
|
8
|
+
attr_accessor :children
|
9
|
+
attr_accessor :name
|
10
|
+
|
11
|
+
#
|
12
|
+
# load the menu.txt file and build the in-memory menu array
|
13
|
+
#
|
14
|
+
def load(menu_file_path)
|
15
|
+
File.open(menu_file_path) do |file|
|
16
|
+
parse_menu(file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(name, parent=nil)
|
21
|
+
self.name = name
|
22
|
+
self.parent = parent
|
23
|
+
self.children = []
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
## public methods
|
28
|
+
##
|
29
|
+
|
30
|
+
#
|
31
|
+
# returns the menu under the item that matches item_name.
|
32
|
+
#
|
33
|
+
def submenu(item_name=nil)
|
34
|
+
if item_name
|
35
|
+
self.children.detect {|child| child.name == item_name}
|
36
|
+
else
|
37
|
+
self.children
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# returns path from root to this leaf as an array
|
43
|
+
#
|
44
|
+
def path
|
45
|
+
@path ||= begin
|
46
|
+
if parent == nil
|
47
|
+
[]
|
48
|
+
else
|
49
|
+
parent.path + [name]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def path_str
|
55
|
+
@path_str ||= path.join('/')
|
56
|
+
end
|
57
|
+
|
58
|
+
def each(&block)
|
59
|
+
children.each(&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def size
|
63
|
+
children.size
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# returns true if menu's path starts with +path_prefix+
|
68
|
+
#
|
69
|
+
def path_starts_with?(path_prefix)
|
70
|
+
array_starts_with?(path, path_prefix)
|
71
|
+
end
|
72
|
+
|
73
|
+
def path_prefix_of?(full_path)
|
74
|
+
array_starts_with?(full_path, path)
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# returns true if this menu item is the terminus menu item for path.
|
79
|
+
# (meaning that there are no children that match more path segments)
|
80
|
+
#
|
81
|
+
def leaf_for_path?(path)
|
82
|
+
return false unless path_prefix_of?(path)
|
83
|
+
next_path_segment = (path - self.path).first
|
84
|
+
return false if next_path_segment.nil?
|
85
|
+
return !children.detect {|i| i.name == next_path_segment}
|
86
|
+
end
|
87
|
+
|
88
|
+
def inspect(indent=0)
|
89
|
+
lines = []
|
90
|
+
lines << ' '*indent + '- ' + self.name
|
91
|
+
self.children.each do |child|
|
92
|
+
lines << child.inspect(indent+1)
|
93
|
+
end
|
94
|
+
lines.join("\n")
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# private & protected methods
|
99
|
+
#
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
def add_child(name)
|
104
|
+
self.children << Menu.new(name, self)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def array_starts_with?(big_array, small_array)
|
110
|
+
small_array.length.times do |i|
|
111
|
+
if small_array[i] != big_array[i]
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def parse_menu(file)
|
120
|
+
while true
|
121
|
+
item = file.readline
|
122
|
+
if item.strip.chars.any? && item !~ /^\s*#/
|
123
|
+
depth = item.scan(" ").size
|
124
|
+
last_menu_at_depth(depth).add_child(item.strip)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
rescue EOFError
|
128
|
+
# done loading
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# returns the last list of children at the specified depth
|
133
|
+
#
|
134
|
+
def last_menu_at_depth(depth)
|
135
|
+
menu = self
|
136
|
+
depth.times { menu = menu.children.last }
|
137
|
+
menu
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|