motyl

Opinionated blog-aware static site generator written in Ruby
Log | Files | Refs | README | LICENSE

motyl (4556B)


      1 #!/usr/bin/env ruby
      2 ###############################################################################
      3 #                                                                             #
      4 # Motyl 1.0.2                                                                 #
      5 # Copyright (c) 2016-2019, Frederic Cambus                                    #
      6 # https://github.com/fcambus/motyl                                            #
      7 #                                                                             #
      8 # Created: 2016-02-16                                                         #
      9 # Last Updated: 2019-05-07                                                    #
     10 #                                                                             #
     11 # Motyl is released under the BSD 2-Clause license.                           #
     12 # See LICENSE file for details.                                               #
     13 #                                                                             #
     14 ###############################################################################
     15 
     16 require 'date'
     17 require 'fileutils'
     18 require 'kramdown'
     19 require 'mustache'
     20 require 'yaml'
     21 
     22 # Enforce UTF-8 character encoding
     23 Encoding.default_internal = Encoding::UTF_8
     24 Encoding.default_external = Encoding::UTF_8
     25 
     26 # Load and process Markdown file
     27 def markdown(path)
     28   Kramdown::Document.new(
     29     File.read(path),
     30     smart_quotes: %w[apos apos quot quot],
     31     syntax_highlighter: 'rouge'
     32   ).to_html
     33 end
     34 
     35 # Display status message
     36 def status(message)
     37   puts('[' + Time.now.strftime('%X') + '] ' + message)
     38 end
     39 
     40 # Loading configuration
     41 data = {
     42   'version' => 'Motyl 1.0.2',
     43   'updated' => Time.now.strftime('%Y-%m-%dT%XZ'),
     44   'site' => YAML.load_file('motyl.conf'),
     45   'posts' => [],
     46   'categories' => {}
     47 }
     48 
     49 theme = 'themes/' + data['site']['theme'] + '/'
     50 
     51 # Loading templates
     52 templates = {
     53   'categories' => File.read(theme + 'templates/categories.mustache'),
     54   'atom' => File.read(theme + 'templates/atom.mustache'),
     55   'pages' => File.read(theme + 'templates/page.mustache'),
     56   'posts' => File.read(theme + 'templates/post.mustache')
     57 }
     58 
     59 Mustache.template_path = theme + 'templates/'
     60 
     61 def render(directory, templates, data)
     62   Dir.foreach(directory) do |file|
     63     next if ['.', '..'].include?(file)
     64 
     65     extension = File.extname(file)
     66 
     67     if extension == '.md'
     68       basename = File.basename(file, extension)
     69       data['page'] = YAML.load_file(directory + '/' + basename + '.yaml')
     70       data['page']['content'] = Mustache.render(
     71         markdown(directory + '/' + file),
     72         data
     73       )
     74       data['page']['url'] ||= basename + '/'
     75 
     76       status('Rendering ' + data['page']['url'])
     77 
     78       if directory == 'posts'
     79         data['page']['datetime'] =
     80           DateTime.parse(data['page']['date']).strftime('%Y-%m-%dT%XZ')
     81 
     82         data['posts'].push(data['page'])
     83 
     84         data['page']['categoryDisplay'] = []
     85 
     86         # Populate category table
     87         data['page']['categories'].each do |category|
     88           data['categories'][category] ||= []
     89           data['categories'][category].push(data['page'])
     90           data['page']['categoryDisplay'].push(
     91             'category' => category,
     92             'url' => data['site']['categoryMap'][category]
     93           )
     94         end
     95       end
     96 
     97       FileUtils.mkdir_p('public/' + data['page']['url'])
     98       File.write('public/' + data['page']['url'] + 'index.html',
     99                  Mustache.render(templates[directory], data))
    100 
    101       data['page'] = {}
    102     end
    103   end
    104 end
    105 
    106 # Render posts
    107 FileUtils.rm_rf('public')
    108 render('posts', templates, data)
    109 
    110 # Sort post archives
    111 data['posts'].sort! { |a, b| b['date'] <=> a['date'] }
    112 
    113 # Renger pages
    114 render('pages', templates, data)
    115 
    116 # Feed
    117 data['feed'] = data['posts'][0..20]
    118 
    119 File.write('public/atom.xml', Mustache.render(templates['atom'], data))
    120 status('Rendering atom.xml')
    121 data['page'] = {}
    122 
    123 # Categories
    124 data['categories'].keys.each do |category|
    125   category_url = data['site']['categoryMap'][category] + '/'
    126 
    127   data['categories'][category].sort! { |a, b| b['date'] <=> a['date'] }
    128   data['page']['title'] = category
    129   data['page']['url'] = 'categories/' + category_url
    130   data['posts'] = data['categories'][category]
    131 
    132   FileUtils.mkdir_p('public/categories/' + category_url)
    133   File.write('public/categories/' + category_url + 'index.html',
    134              Mustache.render(templates['categories'], data))
    135   status('Rendering ' + category_url)
    136 end
    137 
    138 # Copy static assets
    139 status('Copying assets and static files')
    140 FileUtils.cp_r(Dir.glob(theme + 'assets/*'), 'public')
    141 FileUtils.cp_r(Dir.glob('assets/*'), 'public')