Class: Broadway::Site

Inherits:
Object
  • Object
show all
Defined in:
lib/broadway/site.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Site

Initialize the site

+config+ is a Hash containing site configurations details

Returns <Site>



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/broadway/site.rb', line 15

def initialize(config)
  merge_extra_config!(config)
  
  config.recursive_symbolize_keys!
  
  self.config          = config.clone
  
  self.source          = config[:source]
  self.dest            = config[:destination]
  self.lsi             = config[:lsi]
  self.pygments        = config[:pygments]
  self.permalink_style = config[:permalink].to_sym
  self.exclude         = config[:exclude] || []
  self.tree            = []
  
  self.reset
  self.setup
end

Instance Attribute Details

#categoriesObject

Returns the value of attribute categories.



4
5
6
# File 'lib/broadway/site.rb', line 4

def categories
  @categories
end

#configObject

Returns the value of attribute config.



4
5
6
# File 'lib/broadway/site.rb', line 4

def config
  @config
end

#destObject

Returns the value of attribute dest.



4
5
6
# File 'lib/broadway/site.rb', line 4

def dest
  @dest
end

#excludeObject

Returns the value of attribute exclude.



4
5
6
# File 'lib/broadway/site.rb', line 4

def exclude
  @exclude
end

#layoutsObject

Returns the value of attribute layouts.



4
5
6
# File 'lib/broadway/site.rb', line 4

def layouts
  @layouts
end

#lsiObject

Returns the value of attribute lsi.



4
5
6
# File 'lib/broadway/site.rb', line 4

def lsi
  @lsi
end

#pagesObject

Returns the value of attribute pages.



4
5
6
# File 'lib/broadway/site.rb', line 4

def pages
  @pages
end

Returns the value of attribute permalink_style.



4
5
6
# File 'lib/broadway/site.rb', line 4

def permalink_style
  @permalink_style
end

#postsObject

Returns the value of attribute posts.



4
5
6
# File 'lib/broadway/site.rb', line 4

def posts
  @posts
end

#pygmentsObject

Returns the value of attribute pygments.



4
5
6
# File 'lib/broadway/site.rb', line 4

def pygments
  @pygments
end

#sourceObject

Returns the value of attribute source.



4
5
6
# File 'lib/broadway/site.rb', line 4

def source
  @source
end

#static_filesObject

Returns the value of attribute static_files.



4
5
6
# File 'lib/broadway/site.rb', line 4

def static_files
  @static_files
end

#tagsObject

Returns the value of attribute tags.



4
5
6
# File 'lib/broadway/site.rb', line 4

def tags
  @tags
end

#treeObject

Returns the value of attribute tree.



4
5
6
# File 'lib/broadway/site.rb', line 4

def tree
  @tree
end

Class Method Details

.generateObject



7
8
9
# File 'lib/broadway/site.rb', line 7

def self.generate
  
end

Instance Method Details

#buildObject



149
150
151
152
153
154
# File 'lib/broadway/site.rb', line 149

def build
  puts "Building site: #{config[:source]} -> #{config[:destination]}"
  self.reset
  self.read
  puts "Successfully built site, nothing was written."
end

#filtered?(path) ⇒ Boolean

Filter out any files/directories that are hidden or backup files (start with “.” or “#” or end with “~”), or contain site content (start with “_”), or are excluded in the site configuration, unless they are web server files such as ‘.htaccess’

Returns:

  • (Boolean)


351
352
353
354
355
356
# File 'lib/broadway/site.rb', line 351

def filtered?(path)
  return true if File.directory?(path)
  file = File.basename(path)
  return true if ['.htaccess'].include?(file)
  ['.', '_', '#'].include?(file[0..0]) || file[-1..-1] == '~' || self.exclude.include?(file)
end

#find(type, method, value) ⇒ Object



358
359
360
361
362
363
364
365
366
# File 'lib/broadway/site.rb', line 358

def find(type, method, value)
  self.send(type.to_s.pluralize).select do |content|
    if content.respond_to?(method)
      content.send(method) == value
    else
      content.send(method.to_s.pluralize).include?(value)
    end
  end
end

#first(type, method, value) ⇒ Object



368
369
370
# File 'lib/broadway/site.rb', line 368

def first(type, method, value)
  find(type, method, value).first
end

#generateObject



156
157
158
159
160
161
# File 'lib/broadway/site.rb', line 156

def generate
  puts "Generating site: #{config[:source]} -> #{config[:destination]}"
  self.render
  self.write
  puts "Successfully generated site: #{config[:source]} -> #{config[:destination]}"
end

#inspectObject



394
395
396
# File 'lib/broadway/site.rb', line 394

def inspect
  "#<Broadway:Site @page_count=#{self.page_count.to_s} @post_count=#{self.post_count.to_s}>"
end

#merge_extra_config!(config) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/broadway/site.rb', line 34

def merge_extra_config!(config)
  locale = File.join("locales", "#{config[:language]}.yml")
  if File.exists?(locale)
    config.merge!(YAML.load_file(locale))
  end
  config_dir = "config"
  return unless File.exists?(config_dir)
  Dir.glob("#{config_dir}/**/*").each do |file|
    next if File.directory?(file)
    path = file.gsub(/config\//, "").split(".")[0..-2].join("")
    ext = File.extname(file)
    if ext =~ /yml/
      data = YAML.load_file(file)
    elsif ext =~ /xml/
      data = parse_children Nokogiri::XML(IO.read(file)).children[0]
    else
      data = IO.read(file)
    end
    next unless data
    name = path.split("/").first
    if path =~ /\//
      config[name] ||= {}
      target = config[name]
      name = path.split("/").last
    else
      target = config
    end
    if target.has_key?(name)
      target[name].merge!(data)
    else
      target[name] = data
    end
  end
end

#new_page(path, options = {}) ⇒ Object



236
237
238
239
240
241
# File 'lib/broadway/site.rb', line 236

def new_page(path, options = {})
  return if path.nil? || path.empty?
  page = Page.new(options.merge(:site => self, :path => path, :process => false))
  self.pages << page
  page
end

#new_post(path, options = {}) ⇒ Object



243
244
245
246
247
248
# File 'lib/broadway/site.rb', line 243

def new_post(path, options = {})
  return if path.nil? || path.empty?
  post = Post.new(options.merge(:site => self, :path => path, :process => false))
  self.posts << post
  post
end

#new_tree(path) ⇒ Object



250
251
252
# File 'lib/broadway/site.rb', line 250

def new_tree(path)
  self.tree.concat parse_tree(Nokogiri::XML(IO.read(path)).root)
end

#node_list(elem, &proc) ⇒ Object



270
271
272
273
274
# File 'lib/broadway/site.rb', line 270

def node_list(elem, &proc)
  return [] unless elem.class == Nokogiri::XML::Element
  str = proc.call(elem)
  [str] + elem.children.inject([]){|a,c| a+node_list(c,&proc)}.map{|e| "#{str}/#{e}"}
end

#page_countObject



386
387
388
# File 'lib/broadway/site.rb', line 386

def page_count
  self.pages ? self.pages.length : 0
end

#page_from_xml(content) ⇒ Object



290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/broadway/site.rb', line 290

def page_from_xml(content)
  # page
  # 1. If a "src" is defined, then we should have already created the post
  # 2. If no "src", then content is specified inline (or there is no content yet)
  page = find_page_by_path(content["src"]) || find_page_by_url(content["url"])
  path = (content["src"] || content["url"] || "").gsub(/^\//, "")
  page ||= new_page(path)
  return unless page
  %w(title image excerpt menu_title tooltip show_children content).each do |key|
    page.data[key] = content[key] if content.has_attribute?(key)
  end
  page
end

#parse_tree(parent) ⇒ Object



254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/broadway/site.rb', line 254

def parse_tree(parent)
  result = []
  return result if parent.nil? || parent.children.nil? || parent.children.empty?
  parent.children.each do |child|
    next unless child.elem?
    content = child.children.empty? ? post_from_xml(child) : page_from_xml(child)
    next unless content
    if content.respond_to?(:children)
      content.children.concat parse_tree(child)
    end
    result << content
  end
  result
end

#post_attr_hash(post_attr) ⇒ Object

Constructs a hash map of Posts indexed by the specified Post attribute

Returns => [<Post>]



325
326
327
328
329
330
331
332
# File 'lib/broadway/site.rb', line 325

def post_attr_hash(post_attr)
  # Build a hash map based on the specified post attribute ( post attr => array of posts )
  # then sort each array in reverse order
  hash = Hash.new { |hash, key| hash[key] = Array.new }
  self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
  hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
  return hash
end

#post_countObject



390
391
392
# File 'lib/broadway/site.rb', line 390

def post_count
  self.posts ? self.posts.length : 0
end

#post_from_xml(content) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/broadway/site.rb', line 276

def post_from_xml(content)
  # post
  # 1. If a "src" is defined, then we should have already created the post
  # 2. If no "src", then content is specified inline (or there is no content yet)
  post = find_post_by_path(content["src"]) || find_post_by_url(content["url"])
  path = (content["src"] || content["url"] || "").gsub(/^\//, "")
  post ||= new_post(path)
  return unless post
  %w(title image excerpt menu_title tooltip show_children content).each do |key|
    post.data[key] = content[key] if content.has_attribute?(key)
  end
  post
end

#processObject

third Do the actual work of processing the site and generating the real deal. Now has 4 phases; reset, read, render, write. This allows rendering to have full site payload available.

Returns nothing



144
145
146
147
# File 'lib/broadway/site.rb', line 144

def process
  build
  generate
end

#readObject



163
164
165
166
# File 'lib/broadway/site.rb', line 163

def read
  self.read_layouts # existing implementation did this at top level only so preserved that
  self.read_directories
end

#read_directories(dir = '') ⇒ Object

Reads the directories and finds posts, pages and static files that will become part of the valid site according to the rules in filter_entries.

The +dir+ String is a relative path used to call this method
         recursively as it descends through directories

Returns nothing



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/broadway/site.rb', line 189

def read_directories(dir = '')
  base = File.join(self.source, dir).gsub(/\/$/, "")
  lists = []
  # RULES:
  # Pages are only index.textile files, or static .html files (TODO)
  # Posts are leaf nodes (name.textile files)
  # Categories are the directory name split plus anything extra defined
  # Pages and Posts can also be obtained via index.xml files
  Dir.glob("#{base}/**/*").each do |path|
    # removes junk, leaves us with .xml, .textile, .html
    next if filtered?(path)
    name = File.basename(path).split(".").first
    ext = File.extname(path).gsub(".", "")
    if name == "index"
      if ext != "xml"
        new_page(path)
      end
    elsif %w(textile markdown).include?(ext)
      new_post(path) 
    else
      self.static_files << StaticFile.new(:site => self, :path => path)
    end
  end
  
  # lists are xml files we've collect, but we want to make sure we've
  # created all the posts we need to beforehand
  list = File.join(config[:source], "index.xml")
  lists << list if File.exists?(list)
  lists.each do |path|
    new_tree(path)
  end
  
  # finally, we have set all the initial variables on the
  # pages and posts we need, now we can process them to find
  # the content and generate the urls
  self.pages.each do |page|
    page.process
    page.categories.each { |c| self.categories[c] << page }
    page.tags.each { |c| self.tags[c] << page }
  end
  self.posts.each do |post|
    post.process
    post.categories.each { |c| self.categories[c] << post }
    post.tags.each { |c| self.tags[c] << post }
  end
end

#read_layouts(dir = '') ⇒ Object

Read all the files in <source>/<dir>/_layouts and create a new Layout object with each one.

Returns nothing



172
173
174
175
176
177
178
179
180
181
# File 'lib/broadway/site.rb', line 172

def read_layouts(dir = '')
  base = File.join(self.source, dir, config[:layouts])
  return unless File.exists?(base)
  entries = []
  Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
  entries.each do |f|
    name = f.split(".")[0..-2].join(".")
    self.layouts[name] = Layout.new(self, base, f)
  end
end

#renderObject

While in Jekyll this method renders the content explicitly, I’m using Sinatra, partly because I like having the flexibility of using their get/post methods, and partly because I don’t have the time/desire to try to hack jekyll to use haml. I’d rather just start over



309
310
311
# File 'lib/broadway/site.rb', line 309

def render
  Runner.new(self).run
end

#resetObject

first



70
71
72
73
74
75
76
77
# File 'lib/broadway/site.rb', line 70

def reset
  self.layouts         = {}
  self.posts           = []
  self.pages           = []
  self.static_files    = []
  self.categories      = Hash.new { |hash, key| hash[key] = [] }
  self.tags            = Hash.new { |hash, key| hash[key] = [] }
end

#setupObject

second this just sets configuration variables on the dependencies, if necessary



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/broadway/site.rb', line 81

def setup
  # Check to see if LSI is enabled.
  require 'classifier' if self.lsi

  # Set the Markdown interpreter (and Maruku self.config, if necessary)
  case self.config[:markdown]
    when 'rdiscount'
      begin
        require 'rdiscount'

        def markdown(content)
          RDiscount.new(content).to_html
        end

      rescue LoadError
        puts 'You must have the rdiscount gem installed first'
      end
    when 'maruku'
      begin
        require 'maruku'

        def markdown(content)
          Maruku.new(content).to_html
        end

        if self.config[:maruku][:use_divs]
          require 'maruku/ext/div'
          puts 'Maruku: Using extended syntax for div elements.'
        end

        if self.config[:maruku][:use_tex]
          require 'maruku/ext/math'
          puts "Maruku: Using LaTeX extension. Images in `#{self.config[:maruku][:png_dir]}`."

          # Switch off MathML output
          MaRuKu::Globals[:html_math_output_mathml] = false
          MaRuKu::Globals[:html_math_engine] = 'none'

          # Turn on math to PNG support with blahtex
          # Resulting PNGs stored in `images/latex`
          MaRuKu::Globals[:html_math_output_png] = true
          MaRuKu::Globals[:html_png_engine] =  self.config[:maruku][:png_engine]
          MaRuKu::Globals[:html_png_dir] = self.config[:maruku][:png_dir]
          MaRuKu::Globals[:html_png_url] = self.config[:maruku][:png_url]
        end
      rescue LoadError
        puts "The maruku gem is required for markdown support!"
      end
    else
      raise "Invalid Markdown processor: '#{self.config[:markdown]}' -- did you mean 'maruku' or 'rdiscount'?"
  end
end

#site_payloadObject

The Hash payload containing site-wide data

Returns => {“time” => <Time>,

"posts" => [<Post>],
"categories" => [<Post>]


339
340
341
342
343
344
345
# File 'lib/broadway/site.rb', line 339

def site_payload
  {"site" => self.config.merge({
      "time"       => Time.now,
      "posts"      => self.posts.sort { |a,b| b <=> a },
      "categories" => post_attr_hash('categories'),
      "tags"       => post_attr_hash('tags')})}
end

#textile(content) ⇒ Object



134
135
136
# File 'lib/broadway/site.rb', line 134

def textile(content)
  RedCloth.new(content).to_html
end

#writeObject

Write static files, pages and posts

Returns nothing



316
317
318
319
320
# File 'lib/broadway/site.rb', line 316

def write
  self.static_files.each do |sf|
    sf.write(self.dest)
  end
end