ruhoh 0.0.2

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,57 @@
1
+ =)
2
+
3
+
4
+ ## Setup/Config
5
+
6
+ Ruhoh.setup is responsible for building the configuration data.
7
+ The config takes a given source directory, parses its \_config.yml
8
+ and builds absolute paths to all the Filesystem API endpoints relative to the source directory.
9
+
10
+ ## Parsers
11
+
12
+ Parsers are small, atomic, functional methods used to process data.
13
+ Parsers follow the Ruhoh interface specification, to locate, extract, and process data relative to the source directory.
14
+
15
+
16
+ ## DB
17
+
18
+ The database is responsible for holding website's main data.
19
+ The database depends on Ruhoh.config.
20
+ It depends on the parsers to parse the source directory in order to return the data.
21
+
22
+
23
+ ## Page
24
+
25
+ The page class models a page in the ruhoh system.
26
+ A page is technically a post object or a page object.
27
+
28
+ - content
29
+ - data (includes yaml front matter + url, id etc.)
30
+ - sub\_layout
31
+ - master\_layout
32
+
33
+ ## Converter
34
+
35
+ The converter is responsible for converting a pages 'content' into HTML.
36
+ A page can be written in various markup formats specified by the page's extension.
37
+
38
+ ## Templater
39
+
40
+ The templater is responsible for building the finished page by
41
+ utilizing the layouts, content, and any embedded templating language expressions.
42
+
43
+
44
+ # Compiler
45
+
46
+ Compiles the full website
47
+
48
+ # Preview
49
+
50
+ Serves a singular page view to a web-server identified by its url.
51
+
52
+
53
+
54
+
55
+
56
+
57
+
data/bin/ruhoh ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+
5
+ require 'ruhoh'
6
+ require 'ruhoh/client'
7
+
8
+ help = <<HELP
9
+ Ruhoh is the best static blog generator known to mankind.
10
+
11
+ Basic Command Line Usage:
12
+ Test
13
+ HELP
14
+
15
+ case ARGV[0]
16
+ when 'new'
17
+ Ruhoh::Client.new_blog(ARGV[1])
18
+ when 'help'
19
+ puts help
20
+ else
21
+ puts help
22
+ end
@@ -0,0 +1,28 @@
1
+ class Ruhoh
2
+
3
+ module Client
4
+ Root = File.expand_path(File.join(File.dirname(__FILE__), '../..'))
5
+
6
+ def self.new_blog(name)
7
+ if name.nil?
8
+ puts "Name must be specified"
9
+ exit 0
10
+ end
11
+
12
+ source_directory = File.join(Root, 'scaffolds/blog')
13
+ target_directory = File.join(Dir.pwd, name)
14
+
15
+ if File.exist?(target_directory)
16
+ puts "#{target_directory} already exists. Specify another directory."
17
+ exit 0
18
+ end
19
+
20
+ FileUtils.mkdir target_directory
21
+ FileUtils.cp_r "#{source_directory}/.", target_directory
22
+
23
+ puts "=> Blog successfully cloned to:"
24
+ puts "=> #{target_directory}"
25
+ end
26
+ end
27
+
28
+ end #Ruhoh
@@ -0,0 +1,58 @@
1
+ class Ruhoh
2
+
3
+ class Compiler
4
+
5
+ def initialize(target_directory = './_compiled')
6
+ Ruhoh::DB.initialize
7
+ @target = target_directory
8
+ @page = Ruhoh::Page.new
9
+ end
10
+
11
+ def compile
12
+ FileUtils.mkdir @target unless File.exist?(@target)
13
+ self.theme
14
+ self.pages
15
+ self.media
16
+ end
17
+
18
+ def pages
19
+ processed = []
20
+
21
+ FileUtils.cd(@target) {
22
+ Ruhoh::DB.posts['dictionary'].merge(Ruhoh::DB.pages).each_value do |p|
23
+ @page.change(p['id'])
24
+
25
+ FileUtils.mkdir_p File.dirname(@page.compiled_path)
26
+ File.open(@page.compiled_path, 'w') { |p| p.puts @page.render }
27
+
28
+ processed << path
29
+ end
30
+ }
31
+
32
+ puts "=> Posts Processed:"
33
+ puts processed
34
+ end
35
+
36
+ def theme
37
+ FileUtils.mkdir_p File.join(@target, Ruhoh.config.asset_path)
38
+ FileUtils.cp_r Ruhoh.paths.theme, File.join(@target, Ruhoh.folders.templates, Ruhoh.folders.themes)
39
+ end
40
+
41
+ def media
42
+ FileUtils.mkdir_p File.join(@target, Ruhoh.folders.media)
43
+ FileUtils.cp_r Ruhoh.paths.media, File.join(@target)
44
+ end
45
+
46
+ def write_data
47
+ File.open(Ruhoh.paths.database + '/posts_dictionary.yml', 'w') { |page|
48
+ page.puts Ruhoh::DB.posts.to_yaml
49
+ }
50
+
51
+ File.open(Ruhoh.paths.database + '/pages_dictionary.yml', 'w') { |page|
52
+ page.puts Ruhoh::DB.pages.to_yaml
53
+ }
54
+ end
55
+
56
+ end #Compiler
57
+
58
+ end #Ruhoh
@@ -0,0 +1,17 @@
1
+ require 'maruku'
2
+
3
+ class Ruhoh
4
+
5
+ module Converter
6
+
7
+ def self.convert(page)
8
+ if ['.md', '.markdown'].include? File.extname(page.data['id']).downcase
9
+ Maruku.new(page.content).to_html
10
+ else
11
+ page.content
12
+ end
13
+ end
14
+
15
+ end #Converter
16
+
17
+ end #Ruhoh
data/lib/ruhoh/db.rb ADDED
@@ -0,0 +1,57 @@
1
+ require "observer"
2
+
3
+ class Ruhoh
4
+
5
+ # Public: Database class for interacting with "data" in Ruhoh.
6
+ #
7
+ class DB
8
+
9
+ class << self
10
+ include Observable
11
+ attr_reader :site, :routes, :posts, :pages, :layouts, :partials
12
+
13
+ # Note this is class-level so you have to call it manually.
14
+ def initialize
15
+ @site = ''
16
+ @routes = ''
17
+ @posts = ''
18
+ @pages = ''
19
+ @layouts = ''
20
+ @partials = ''
21
+ self.update!
22
+ end
23
+
24
+ def update(name)
25
+ self.instance_variable_set("@#{name}",
26
+ case name
27
+ when :site
28
+ Ruhoh::Parsers::Site.generate
29
+ when :routes
30
+ Ruhoh::Parsers::Routes.generate
31
+ when :posts
32
+ Ruhoh::Parsers::Posts.generate
33
+ when :pages
34
+ Ruhoh::Parsers::Pages.generate
35
+ when :layouts
36
+ Ruhoh::Parsers::Layouts.generate
37
+ when :partials
38
+ Ruhoh::Parsers::Partials.generate
39
+ else
40
+ raise "Data type: '#{name}' is not a valid data type."
41
+ end
42
+ )
43
+ changed
44
+ notify_observers(name)
45
+ end
46
+
47
+ def update!
48
+ self.instance_variables.each { |var|
49
+ self.__send__ :update, var.to_s.gsub('@', '').to_sym
50
+ }
51
+ end
52
+
53
+ end #self
54
+
55
+ end #DB
56
+
57
+ end #Ruhoh
data/lib/ruhoh/page.rb ADDED
@@ -0,0 +1,63 @@
1
+ class Ruhoh
2
+
3
+ class Page
4
+ attr_reader :data, :content, :sub_layout, :master_layout
5
+
6
+ # Public: Change this page using an id.
7
+ def change(id)
8
+ @data = nil
9
+ @data = id =~ Regexp.new("^#{Ruhoh.folders.posts}") ? Ruhoh::DB.posts['dictionary'][id] : Ruhoh::DB.pages[id]
10
+ raise "Page #{id} not found in database" unless @data
11
+ @id = id
12
+ end
13
+
14
+ # Public: Change this page using a URL.
15
+ def change_with_url(url)
16
+ url = '/index.html' if url == '/'
17
+ id = Ruhoh::DB.routes[url]
18
+ raise "Page id not found for url: #{url}" unless id
19
+ self.change(id)
20
+ end
21
+
22
+ def render
23
+ self.process_layouts
24
+ self.process_content
25
+ Ruhoh::Templater.expand_and_render(self)
26
+ end
27
+
28
+ def process_layouts
29
+ @sub_layout = Ruhoh::DB.layouts[@data['layout']]
30
+
31
+ if @sub_layout['data']['layout']
32
+ @master_layout = Ruhoh::DB.layouts[@sub_layout['data']['layout']]
33
+ end
34
+ end
35
+
36
+ # We need to pre-process the content data
37
+ # in order to invoke converters on the result.
38
+ # Converters (markdown) always choke on the templating language.
39
+ def process_content
40
+ @content = Ruhoh::Utils.parse_file(Ruhoh.paths.site_source, @id)['content']
41
+ @content = Ruhoh::Templater.render(@content, self)
42
+ @content = Ruhoh::Converter.convert(self)
43
+ end
44
+
45
+ # Public: Return page attributes suitable for inclusion in the
46
+ # 'payload' of the given templater.
47
+ def attributes
48
+ @data['content'] = @content
49
+ @data
50
+ end
51
+
52
+ # Public: Formats the path to the compiled file based on the URL.
53
+ #
54
+ # Returns: [String] The relative path to the compiled file for this page.
55
+ def compiled_path
56
+ path = CGI.unescape(@data['url']).gsub(/^\//, '') #strip leading slash.
57
+ path += '/index.html' unless path =~ /\.html$/
58
+ path
59
+ end
60
+
61
+ end #Page
62
+
63
+ end #Ruhoh
@@ -0,0 +1,25 @@
1
+ class Ruhoh
2
+
3
+ module Parsers
4
+
5
+ module Layouts
6
+
7
+ # Generate layouts only from the active theme.
8
+ def self.generate
9
+ layouts = {}
10
+ FileUtils.cd(Ruhoh.paths.layouts) {
11
+ Dir.glob("**/*.*") { |filename|
12
+ next if FileTest.directory?(filename)
13
+ next if ['_','.'].include? filename[0]
14
+ id = File.basename(filename, File.extname(filename))
15
+ layouts[id] = Ruhoh::Utils.parse_file(Ruhoh.paths.layouts, filename)
16
+ }
17
+ }
18
+ layouts
19
+ end
20
+
21
+ end #Layouts
22
+
23
+ end #Parsers
24
+
25
+ end #Ruhoh
@@ -0,0 +1,71 @@
1
+ class Ruhoh
2
+
3
+ module Parsers
4
+
5
+ module Pages
6
+
7
+ # Public: Generate the Pages dictionary.
8
+ #
9
+ def self.generate
10
+ raise "Ruhoh.config cannot be nil.\n To set config call: Ruhoh.setup" unless Ruhoh.config
11
+ puts "=> Generating Pages..."
12
+
13
+ invalid_pages = []
14
+ dictionary = {}
15
+ total_pages = 0
16
+ FileUtils.cd(Ruhoh.paths.site_source) {
17
+ Dir.glob("**/*.*") { |filename|
18
+ next unless self.is_valid_page?(filename)
19
+ total_pages += 1
20
+
21
+ File.open(filename) do |page|
22
+ front_matter = page.read.match(Ruhoh::Utils::FMregex)
23
+ if !front_matter
24
+ invalid_pages << filename ; next
25
+ end
26
+
27
+ data = YAML.load(front_matter[0].gsub(/---\n/, "")) || {}
28
+ data['id'] = filename
29
+ data['url'] = self.permalink(data)
30
+ data['title'] = data['title'] || self.titleize(filename)
31
+
32
+ dictionary[filename] = data
33
+ end
34
+ }
35
+ }
36
+
37
+ if invalid_pages.empty?
38
+ puts "=> #{total_pages - invalid_pages.count }/#{total_pages} pages processed."
39
+ else
40
+ puts "=> Invalid pages not processed:"
41
+ puts invalid_pages.to_yaml
42
+ end
43
+
44
+ dictionary
45
+ end
46
+
47
+ def self.is_valid_page?(filename)
48
+ return false if FileTest.directory?(filename)
49
+ return false if ['_', '.'].include? filename[0]
50
+ return false if Ruhoh.filters.pages['names'].include? filename
51
+ Ruhoh.filters.pages['regexes'].each {|regex| return false if filename =~ regex }
52
+ true
53
+ end
54
+
55
+ def self.titleize(filename)
56
+ File.basename( filename, File.extname(filename) ).gsub(/[\W\_]/, ' ').gsub(/\b\w/){$&.upcase}
57
+ end
58
+
59
+ def self.permalink(page)
60
+ url = '/' + page['id'].gsub(File.extname(page['id']), '.html')
61
+
62
+ # sanitize url
63
+ url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
64
+ url
65
+ end
66
+
67
+ end # Pages
68
+
69
+ end #Parsers
70
+
71
+ end #Ruhoh
@@ -0,0 +1,29 @@
1
+ class Ruhoh
2
+
3
+ module Parsers
4
+
5
+ module Partials
6
+
7
+ def self.generate
8
+ self.process(Ruhoh.paths.global_partials).merge( self.process(Ruhoh.paths.partials) )
9
+ end
10
+
11
+ def self.process(path)
12
+ return {} unless File.exist?(path)
13
+
14
+ partials = {}
15
+ FileUtils.cd(path) {
16
+ Dir.glob("**/*").each { |filename|
17
+ next if FileTest.directory?(filename)
18
+ next if ['.'].include? filename[0]
19
+ partials[filename] = File.open(filename).read
20
+ }
21
+ }
22
+ partials
23
+ end
24
+
25
+ end #Partials
26
+
27
+ end #Parsers
28
+
29
+ end #Ruhoh
@@ -0,0 +1,209 @@
1
+ class Ruhoh
2
+
3
+ module Parsers
4
+
5
+ module Posts
6
+
7
+ MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
8
+
9
+ # Public: Generate the Posts dictionary.
10
+ #
11
+ def self.generate
12
+ raise "Ruhoh.config cannot be nil.\n To set config call: Ruhoh.setup" unless Ruhoh.config
13
+ puts "=> Generating Posts..."
14
+
15
+ dictionary, invalid_posts = process_posts
16
+ ordered_posts = []
17
+ dictionary.each_value { |val| ordered_posts << val }
18
+
19
+ ordered_posts.sort! {
20
+ |a,b| Date.parse(b['date']) <=> Date.parse(a['date'])
21
+ }
22
+
23
+ data = {
24
+ 'dictionary' => dictionary,
25
+ 'chronological' => build_chronology(ordered_posts),
26
+ 'collated' => collate(ordered_posts),
27
+ 'tags' => parse_tags(ordered_posts),
28
+ 'categories' => parse_categories(ordered_posts)
29
+ }
30
+
31
+ if invalid_posts.empty?
32
+ puts "=> #{dictionary.count}/#{dictionary.count + invalid_posts.count} posts processed."
33
+ else
34
+ puts "=> Invalid posts not processed:"
35
+ puts invalid_posts.to_yaml
36
+ end
37
+
38
+ data
39
+ end
40
+
41
+ def self.process_posts
42
+ dictionary = {}
43
+ invalid_posts = []
44
+
45
+ FileUtils.cd(Ruhoh.paths.site_source) {
46
+ Dir.glob("#{Ruhoh.folders.posts}/**/*.*") { |filename|
47
+ next if FileTest.directory?(filename)
48
+ next if ['.'].include? filename[0]
49
+
50
+ File.open(filename) do |page|
51
+ front_matter = page.read.match(Ruhoh::Utils::FMregex)
52
+ if !front_matter
53
+ invalid_posts << filename ; next
54
+ end
55
+
56
+ post = YAML.load(front_matter[0].gsub(/---\n/, "")) || {}
57
+
58
+ m, path, file_date, file_slug, ext = *filename.match(MATCHER)
59
+ post['date'] = post['date'] || file_date
60
+
61
+ ## Test for valid date
62
+ begin
63
+ Time.parse(post['date'])
64
+ rescue
65
+ puts "Invalid date format on: #{filename}"
66
+ puts "Date should be: YYYY/MM/DD"
67
+ invalid_posts << filename
68
+ next
69
+ end
70
+
71
+ post['id'] = filename
72
+ post['title'] = post['title'] || self.titleize(file_slug)
73
+ post['url'] = self.permalink(post)
74
+ dictionary[filename] = post
75
+ end
76
+ }
77
+ }
78
+
79
+ [dictionary, invalid_posts]
80
+ end
81
+
82
+ # my-post-title ===> My Post Title
83
+ def self.titleize(file_slug)
84
+ file_slug.gsub(/[\W\_]/, ' ').gsub(/\b\w/){$&.upcase}
85
+ end
86
+
87
+ # Another blatently stolen method from Jekyll
88
+ def self.permalink(post)
89
+ date = Date.parse(post['date'])
90
+ title = post['title'].downcase.gsub(' ', '-').gsub('.','')
91
+ format = case (post['permalink'] || Ruhoh.config.permalink)
92
+ when :pretty
93
+ "/:categories/:year/:month/:day/:title/"
94
+ when :none
95
+ "/:categories/:title.html"
96
+ when :date
97
+ "/:categories/:year/:month/:day/:title.html"
98
+ else
99
+ post['permalink'] || Ruhoh.config.permalink
100
+ end
101
+
102
+ url = {
103
+ "year" => date.strftime("%Y"),
104
+ "month" => date.strftime("%m"),
105
+ "day" => date.strftime("%d"),
106
+ "title" => CGI::escape(title),
107
+ "i_day" => date.strftime("%d").to_i.to_s,
108
+ "i_month" => date.strftime("%m").to_i.to_s,
109
+ "categories" => Array(post['categories'] || post['category']).join('/'),
110
+ "output_ext" => 'html' # what's this for?
111
+ }.inject(format) { |result, token|
112
+ result.gsub(/:#{Regexp.escape token.first}/, token.last)
113
+ }.gsub(/\/\//, "/")
114
+
115
+ # sanitize url
116
+ url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
117
+ url += "/" if url =~ /\/$/
118
+ url
119
+ end
120
+
121
+ def self.build_chronology(posts)
122
+ posts.map { |post| post['id'] }
123
+ end
124
+
125
+ # Internal: Create a collated posts data structure.
126
+ #
127
+ # posts - Required [Array]
128
+ # Must be sorted chronologically beforehand.
129
+ #
130
+ # [{ 'year': year,
131
+ # 'months' : [{ 'month' : month,
132
+ # 'posts': [{}, {}, ..] }, ..] }, ..]
133
+ #
134
+ def self.collate(posts)
135
+ collated = []
136
+ posts.each_with_index do |post, i|
137
+ thisYear = Time.parse(post['date']).strftime('%Y')
138
+ thisMonth = Time.parse(post['date']).strftime('%B')
139
+ if posts[i-1]
140
+ prevYear = Time.parse(posts[i-1]['date']).strftime('%Y')
141
+ prevMonth = Time.parse(posts[i-1]['date']).strftime('%B')
142
+ end
143
+
144
+ if(prevYear == thisYear)
145
+ if(prevMonth == thisMonth)
146
+ collated.last['months'].last['posts'] << post['id'] # append to last year & month
147
+ else
148
+ collated.last['months'] << {
149
+ 'month' => thisMonth,
150
+ 'posts' => [post['id']]
151
+ } # create new month
152
+ end
153
+ else
154
+ collated << {
155
+ 'year' => thisYear,
156
+ 'months' => [{
157
+ 'month' => thisMonth,
158
+ 'posts' => [post['id']]
159
+ }]
160
+ } # create new year & month
161
+ end
162
+
163
+ end
164
+
165
+ collated
166
+ end
167
+
168
+ def self.parse_tags(posts)
169
+ tags = {}
170
+
171
+ posts.each do |post|
172
+ Array(post['tags']).each do |tag|
173
+ if tags[tag]
174
+ tags[tag]['count'] += 1
175
+ else
176
+ tags[tag] = { 'count' => 1, 'name' => tag, 'posts' => [] }
177
+ end
178
+
179
+ tags[tag]['posts'] << post['id']
180
+ end
181
+ end
182
+ tags
183
+ end
184
+
185
+ def self.parse_categories(posts)
186
+ categories = {}
187
+
188
+ posts.each do |post|
189
+ cats = post['categories'] ? post['categories'] : Array(post['category']).join('/')
190
+
191
+ Array(cats).each do |cat|
192
+ cat = Array(cat).join('/')
193
+ if categories[cat]
194
+ categories[cat]['count'] += 1
195
+ else
196
+ categories[cat] = { 'count' => 1, 'name' => cat, 'posts' => [] }
197
+ end
198
+
199
+ categories[cat]['posts'] << post['id']
200
+ end
201
+ end
202
+ categories
203
+ end
204
+
205
+ end # Post
206
+
207
+ end #Parsers
208
+
209
+ end #Ruhoh
@@ -0,0 +1,24 @@
1
+ class Ruhoh
2
+
3
+ module Parsers
4
+
5
+ module Routes
6
+
7
+ #[{"url" => "id"}, ... ]
8
+ def self.generate
9
+ routes = {}
10
+ Ruhoh::Parsers::Pages.generate.each_value { |page|
11
+ routes[page['url']] = page['id']
12
+ }
13
+ Ruhoh::Parsers::Posts.generate['dictionary'].each_value { |page|
14
+ routes[page['url']] = page['id']
15
+ }
16
+
17
+ routes
18
+ end
19
+
20
+ end #Routes
21
+
22
+ end #Parsers
23
+
24
+ end #Ruhoh
@@ -0,0 +1,25 @@
1
+ class Ruhoh
2
+
3
+ module Parsers
4
+
5
+ # Sitewide data hash + configuration file.
6
+ module Site
7
+
8
+ def self.generate
9
+ site = File.join(Ruhoh.paths.site_source, Ruhoh.files.site)
10
+ site = File.exist?(site) ? File.open(site).read : ''
11
+ site = YAML.load(site) || {}
12
+
13
+ config = File.join(Ruhoh.paths.site_source, Ruhoh.files.config)
14
+ config = File.exist?(config) ? File.open(config).read : ''
15
+ config = YAML.load(config) || {}
16
+
17
+ site['config'] = config
18
+ site
19
+ end
20
+
21
+ end #Site
22
+
23
+ end #Parsers
24
+
25
+ end #Ruhoh
@@ -0,0 +1,36 @@
1
+ class Ruhoh
2
+
3
+ # Public: Rack application used to render singular pages via their URL.
4
+ #
5
+ # Examples
6
+ #
7
+ # In config.ru:
8
+ #
9
+ # require 'ruhoh'
10
+ #
11
+ # Ruhoh.setup
12
+ # use Rack::Static, {:root => '.', :urls => ['/_media', "/#{Ruhoh.folders.templates}"]}
13
+ # run Ruhoh::Preview.new
14
+ #
15
+ class Preview
16
+
17
+ def initialize
18
+ Ruhoh::DB.initialize
19
+ @page = Ruhoh::Page.new
20
+ Ruhoh::Watch.start
21
+ end
22
+
23
+ def call(env)
24
+ return favicon if env['PATH_INFO'] == '/favicon.ico'
25
+
26
+ @page.change_with_url(env['PATH_INFO'])
27
+ [200, {'Content-Type' => 'text/html'}, [@page.render]]
28
+ end
29
+
30
+ def favicon
31
+ [200, {'Content-Type' => 'image/x-icon'}, ['']]
32
+ end
33
+
34
+ end #Preview
35
+
36
+ end #Ruhoh
@@ -0,0 +1,109 @@
1
+ require 'pp'
2
+
3
+ class Ruhoh
4
+
5
+ class HelperMustache < Mustache
6
+
7
+ class HelperContext < Context
8
+
9
+ # Overload find method to catch helper expressions
10
+ def find(obj, key, default = nil)
11
+ return super unless key.to_s.index('?')
12
+
13
+ puts "=> Executing helper: #{key}"
14
+ context, helper = key.to_s.split('?')
15
+ context = context.empty? ? obj : super(obj, context)
16
+
17
+ self.mustache_in_stack.__send__ helper, context
18
+ end
19
+
20
+ end #HelperContext
21
+
22
+ def context
23
+ @context ||= HelperContext.new(self)
24
+ end
25
+
26
+ def partial(name)
27
+ Ruhoh::DB.partials[name.to_s]
28
+ end
29
+
30
+ def debug(sub_context)
31
+ puts "=>DEBUG:"
32
+ puts sub_context.class
33
+ puts sub_context.inspect
34
+ "<pre>#{sub_context.class}\n#{sub_context.pretty_inspect}</pre>"
35
+ end
36
+
37
+ def to_tags(sub_context)
38
+ if sub_context.is_a?(Array)
39
+ sub_context.map { |id|
40
+ self.context['_posts']['tags'][id] if self.context['_posts']['tags'][id]
41
+ }
42
+ else
43
+ tags = []
44
+ self.context['_posts']['tags'].each_value { |tag|
45
+ tags << tag
46
+ }
47
+ tags
48
+ end
49
+ end
50
+
51
+ def to_posts(sub_context)
52
+ sub_context = sub_context.is_a?(Array) ? sub_context : self.context['_posts']['chronological']
53
+
54
+ sub_context.map { |id|
55
+ self.context['_posts']['dictionary'][id] if self.context['_posts']['dictionary'][id]
56
+ }
57
+ end
58
+
59
+ def to_pages(sub_context)
60
+ puts "=> call: pages_list with context: #{sub_context}"
61
+ pages = []
62
+ if sub_context.is_a?(Array)
63
+ sub_context.each do |id|
64
+ if self.context[:pages][id]
65
+ pages << self.context[:pages][id]
66
+ end
67
+ end
68
+ else
69
+ self.context[:pages].each_value {|page| pages << page }
70
+ end
71
+ pages
72
+ end
73
+
74
+ def to_categories(sub_context)
75
+ if sub_context.is_a?(Array)
76
+ sub_context.map { |id|
77
+ self.context['_posts']['categories'][id] if self.context['_posts']['categories'][id]
78
+ }
79
+ else
80
+ cats = []
81
+ self.context['_posts']['categories'].each_value { |cat|
82
+ cats << cat
83
+ }
84
+ cats
85
+ end
86
+ end
87
+
88
+ def analytics
89
+ analytics_config = self.context['site']['config']['analytics']
90
+ return '' unless analytics_config && analytics_config['provider']
91
+ code = self.partial("analytics-providers/#{analytics_config['provider']}")
92
+ return "<h2 style='color:red'>!Analytics Provider partial for '#{analytics_config['provider']}' not found </h2>" if code.nil?
93
+
94
+ self.render(code)
95
+ end
96
+
97
+ def comments
98
+ comments_config = self.context['site']['config']['comments']
99
+ return '' unless comments_config && comments_config['provider']
100
+ code = self.partial("comments-providers/#{comments_config['provider']}")
101
+ puts "comments-provders/#{comments_config['provider']}"
102
+ return "<h2 style='color:red'>!Comments Provider partial for '#{comments_config['provider']}' not found </h2>" if code.nil?
103
+
104
+ self.render(code)
105
+ end
106
+
107
+ end #HelperMustache
108
+
109
+ end #Ruhoh
@@ -0,0 +1,39 @@
1
+ class Ruhoh
2
+
3
+ module Templater
4
+
5
+ def self.build_payload(page)
6
+ {
7
+ "page" => page.attributes,
8
+ "site" => Ruhoh::DB.site,
9
+ "pages" => Ruhoh::DB.pages,
10
+ "_posts" => Ruhoh::DB.posts,
11
+ "ASSET_PATH" => Ruhoh.config.asset_path
12
+ }
13
+ end
14
+
15
+ def self.expand_and_render(page)
16
+ self.render(self.expand(page), page)
17
+ end
18
+
19
+ def self.render(output, page)
20
+ Ruhoh::HelperMustache.render(output, self.build_payload(page))
21
+ end
22
+
23
+ # Expand the page.
24
+ # Places page content into sub-template then into master template if available.
25
+ def self.expand(page)
26
+ output = page.sub_layout['content'].gsub(Ruhoh::Utils::ContentRegex, page.content)
27
+
28
+ # An undefined master means the page/post layouts is only one deep.
29
+ # This means it expects to load directly into a master template.
30
+ if page.master_layout && page.master_layout['content']
31
+ output = page.master_layout['content'].gsub(Ruhoh::Utils::ContentRegex, output);
32
+ end
33
+
34
+ output
35
+ end
36
+
37
+ end #Templater
38
+
39
+ end #Ruhoh
@@ -0,0 +1,28 @@
1
+ class Ruhoh
2
+
3
+ module Utils
4
+
5
+ FMregex = /^---\n(.|\n)*---\n/
6
+ ContentRegex = /\{\{\s*content\s*\}\}/i
7
+
8
+ # Relative file_path from site_source
9
+ def self.parse_file(*args)
10
+ path = File.__send__ :join, args
11
+
12
+ raise "File not found: #{path}" unless File.exist?(path)
13
+
14
+ page = File.open(path).read
15
+ front_matter = page.match(FMregex)
16
+ raise "Invalid Frontmatter" unless front_matter
17
+
18
+ data = YAML.load(front_matter[0].gsub(/---\n/, "")) || {}
19
+
20
+ {
21
+ "data" => data,
22
+ "content" => page.gsub(FMregex, '')
23
+ }
24
+ end
25
+
26
+ end
27
+
28
+ end #Ruhoh
@@ -0,0 +1,3 @@
1
+ class Ruhoh
2
+ Version = VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,57 @@
1
+ require 'directory_watcher'
2
+
3
+ class Ruhoh
4
+ module Watch
5
+
6
+ # Internal: Watch website source directory for file changes.
7
+ # The observer triggers data regeneration as files change
8
+ # in order to keep the data up to date in real time.
9
+ #
10
+ # Returns: Nothing
11
+ def self.start
12
+ raise "Ruhoh.config cannot be nil.\n To set config call: Ruhoh.setup" unless Ruhoh.config
13
+ puts "=> Start watching: #{Ruhoh.paths.site_source}"
14
+ glob = ''
15
+
16
+ # Watch all files + all sub directories except for special folders e.g '_database'
17
+ Dir.chdir(Ruhoh.paths.site_source) {
18
+ dirs = Dir['*'].select { |x| File.directory?(x) }
19
+ dirs -= [Ruhoh.folders.database]
20
+ dirs = dirs.map { |x| "#{x}/**/*" }
21
+ dirs += ['*']
22
+ glob = dirs
23
+ }
24
+
25
+ dw = DirectoryWatcher.new(Ruhoh.paths.site_source, {
26
+ :glob => glob,
27
+ :pre_load => true
28
+ })
29
+ dw.interval = 1
30
+ dw.add_observer {|*args|
31
+ args.each {|event|
32
+ path = event['path'].gsub(Ruhoh.paths.site_source, '')
33
+ puts path
34
+ if path =~ Regexp.new("^\/?#{Ruhoh.folders.posts}")
35
+ puts "Watch: update posts"
36
+ Ruhoh::DB.update(:posts)
37
+ Ruhoh::DB.update(:routes)
38
+ elsif path =~ Regexp.new("^\/?#{Ruhoh.folders.templates}")
39
+ puts "Watch: update themes"
40
+ Ruhoh::DB.update(:layouts)
41
+ Ruhoh::DB.update(:partials)
42
+ else
43
+ puts "Watch: update pages"
44
+ Ruhoh::DB.update(:pages)
45
+ Ruhoh::DB.update(:routes)
46
+ end
47
+
48
+ t = Time.now.strftime("%H:%M:%S")
49
+ puts "[#{t}] regeneration: #{args.size} files changed"
50
+ }
51
+ }
52
+
53
+ dw.start
54
+ end
55
+
56
+ end #Watch
57
+ end #Ruhoh
data/lib/ruhoh.rb ADDED
@@ -0,0 +1,84 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'time'
4
+ require 'cgi'
5
+ require 'fileutils'
6
+
7
+ require 'mustache'
8
+
9
+ require 'ruhoh/utils'
10
+ require 'ruhoh/parsers/posts'
11
+ require 'ruhoh/parsers/pages'
12
+ require 'ruhoh/parsers/routes'
13
+ require 'ruhoh/parsers/layouts'
14
+ require 'ruhoh/parsers/partials'
15
+ require 'ruhoh/parsers/site'
16
+ require 'ruhoh/db'
17
+ require 'ruhoh/templaters/helper_mustache'
18
+ require 'ruhoh/templaters/templater'
19
+ require 'ruhoh/converters/converter'
20
+ require 'ruhoh/page'
21
+ require 'ruhoh/preview'
22
+ require 'ruhoh/watch'
23
+
24
+ class Ruhoh
25
+
26
+ class << self; attr_reader :folders, :files, :config, :paths, :filters end
27
+
28
+ Folders = Struct.new(:database, :posts, :templates, :themes, :layouts, :partials, :media)
29
+ Files = Struct.new(:site, :config)
30
+ Filters = Struct.new(:posts, :pages, :static)
31
+ Config = Struct.new(:permalink, :theme, :asset_path)
32
+ Paths = Struct.new(
33
+ :site_source,
34
+ :database,
35
+ :posts,
36
+ :theme,
37
+ :layouts,
38
+ :partials,
39
+ :global_partials,
40
+ :media
41
+ )
42
+
43
+ # Public: Setup Ruhoh utilities relative to the current directory
44
+ # of the application and its corresponding ruhoh.json file.
45
+ #
46
+ def self.setup
47
+ @folders = Folders.new('_database', '_posts', '_templates', 'themes', 'layouts', 'partials', "_media")
48
+ @files = Files.new('_site.yml', '_config.yml')
49
+ @filters = Filters.new
50
+ @config = Config.new
51
+ @paths = Paths.new
52
+
53
+ config = { 'site_source' => Dir.getwd }
54
+ site_config = YAML.load_file( File.join(config['site_source'], '_config.yml') )
55
+
56
+ @config.permalink = site_config['permalink'] || :date
57
+ @config.theme = site_config['theme']
58
+ @config.asset_path = File.join('/', @folders.templates, @folders.themes, @config.theme)
59
+
60
+ @paths.site_source = config['site_source']
61
+ @paths.database = self.absolute_path(@folders.database)
62
+ @paths.posts = self.absolute_path(@folders.posts)
63
+
64
+ @paths.theme = self.absolute_path(@folders.templates, @folders.themes, @config.theme)
65
+ @paths.layouts = self.absolute_path(@folders.templates, @folders.themes, @config.theme, @folders.layouts)
66
+ @paths.partials = self.absolute_path(@folders.templates, @folders.themes, @config.theme, @folders.partials)
67
+ @paths.global_partials = self.absolute_path(@folders.templates, @folders.partials)
68
+ @paths.media = self.absolute_path(@folders.media)
69
+
70
+
71
+ # filename filters:
72
+ @filters.pages = { 'names' => [], 'regexes' => [] }
73
+ exclude = ['Gemfile', 'Gemfile.lock', 'config.ru', 'README.md']
74
+ exclude.each {|node|
75
+ @filters.pages['names'] << node if node.is_a?(String)
76
+ @filters.pages['regexes'] << node if node.is_a?(Regexp)
77
+ }
78
+ end
79
+
80
+ def self.absolute_path(*args)
81
+ File.__send__ :join, args.unshift(self.paths.site_source)
82
+ end
83
+
84
+ end # Ruhoh
data/ruhoh.gemspec ADDED
@@ -0,0 +1,47 @@
1
+ $LOAD_PATH.unshift 'lib'
2
+ require 'ruhoh/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "ruhoh"
6
+ s.version = Ruhoh::Version
7
+ s.date = Time.now.strftime('%Y-%m-%d')
8
+ s.license = "http://unlicense.org/"
9
+ s.summary = 'Ruby based library to process your Ruhoh static blog.'
10
+ s.homepage = "http://github.com/plusjade/ruhoh.rb"
11
+ s.email = "plusjade@gmail.com"
12
+ s.authors = ['Jade Dominguez']
13
+ s.description = 'Ruhoh is a Universal API for your static blog.'
14
+ s.executables = ["ruhoh"]
15
+
16
+ # dependencies defined in Gemfile
17
+ s.add_dependency 'rack'
18
+ s.add_dependency 'mustache'
19
+ s.add_dependency 'directory_watcher'
20
+ s.add_dependency 'maruku'
21
+
22
+ # = MANIFEST =
23
+ s.files = %w[
24
+ README.md
25
+ bin/ruhoh
26
+ lib/ruhoh.rb
27
+ lib/ruhoh/client.rb
28
+ lib/ruhoh/compiler.rb
29
+ lib/ruhoh/converters/converter.rb
30
+ lib/ruhoh/db.rb
31
+ lib/ruhoh/page.rb
32
+ lib/ruhoh/parsers/layouts.rb
33
+ lib/ruhoh/parsers/pages.rb
34
+ lib/ruhoh/parsers/partials.rb
35
+ lib/ruhoh/parsers/posts.rb
36
+ lib/ruhoh/parsers/routes.rb
37
+ lib/ruhoh/parsers/site.rb
38
+ lib/ruhoh/preview.rb
39
+ lib/ruhoh/templaters/helper_mustache.rb
40
+ lib/ruhoh/templaters/templater.rb
41
+ lib/ruhoh/utils.rb
42
+ lib/ruhoh/version.rb
43
+ lib/ruhoh/watch.rb
44
+ ruhoh.gemspec
45
+ ]
46
+ # = MANIFEST =
47
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruhoh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jade Dominguez
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-07 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: &70125560161900 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70125560161900
25
+ - !ruby/object:Gem::Dependency
26
+ name: mustache
27
+ requirement: &70125560161480 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70125560161480
36
+ - !ruby/object:Gem::Dependency
37
+ name: directory_watcher
38
+ requirement: &70125560190740 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70125560190740
47
+ - !ruby/object:Gem::Dependency
48
+ name: maruku
49
+ requirement: &70125560190320 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70125560190320
58
+ description: Ruhoh is a Universal API for your static blog.
59
+ email: plusjade@gmail.com
60
+ executables:
61
+ - ruhoh
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - README.md
66
+ - bin/ruhoh
67
+ - lib/ruhoh.rb
68
+ - lib/ruhoh/client.rb
69
+ - lib/ruhoh/compiler.rb
70
+ - lib/ruhoh/converters/converter.rb
71
+ - lib/ruhoh/db.rb
72
+ - lib/ruhoh/page.rb
73
+ - lib/ruhoh/parsers/layouts.rb
74
+ - lib/ruhoh/parsers/pages.rb
75
+ - lib/ruhoh/parsers/partials.rb
76
+ - lib/ruhoh/parsers/posts.rb
77
+ - lib/ruhoh/parsers/routes.rb
78
+ - lib/ruhoh/parsers/site.rb
79
+ - lib/ruhoh/preview.rb
80
+ - lib/ruhoh/templaters/helper_mustache.rb
81
+ - lib/ruhoh/templaters/templater.rb
82
+ - lib/ruhoh/utils.rb
83
+ - lib/ruhoh/version.rb
84
+ - lib/ruhoh/watch.rb
85
+ - ruhoh.gemspec
86
+ homepage: http://github.com/plusjade/ruhoh.rb
87
+ licenses:
88
+ - http://unlicense.org/
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.17
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Ruby based library to process your Ruhoh static blog.
111
+ test_files: []