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/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
@@ -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