ruhoh 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []