motyl

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

motyl (3792B)


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