class Solutus

TODO: Add support for tags TODO: Make stress tests TODO: Fix bug : day being off with time in file

Constants

ARCHIVE
BLOG_BUILD_PATH
BLOG_DATE_DATA
BLOG_DATE_FORMAT
BLOG_TEMPLATE
BUILD_PATH
CACHE
DAY_ENDINGS
DEFAULT_TEMPLATE
LOG_FILE
PASSWORD_FILE
POSTS_PATH
SETTINGS_FILE
SITE_PAGES_PATH
SITE_PATH
STATIC_PATH
TEMPLATES_PATH
VERSION

Public Class Methods

blog_urls() click to toggle source
# File lib/solutus.rb, line 535
def self.blog_urls
    @@blog_urls
end
build() click to toggle source
# File lib/solutus.rb, line 613
    def self.build
        puts "Solutus is building the static site"
        settings_data = load_settings
        if settings_data == false
            return
        end
        
        if File.directory?(BUILD_PATH)
            FileUtils.remove_dir(BUILD_PATH)
        end
        Dir.mkdir(BUILD_PATH)
        Dir.mkdir(SITE_PATH)
        copy_static

        @@templates = Hash.new
        Dir.entries(TEMPLATES_PATH).each do |entry|
            next if entry == "." || entry == ".."
            puts "Loading template #{entry}"
            path = File.join(TEMPLATES_PATH, entry)
            if File.file?(path)
                contents = ""
                f = File.open(path, "r")
                f.each_line do |line|
                    contents += line
                end
                @@templates[entry.split(".")[0]] = contents
            end
        end

        yaml_data = Hash.new

        @@blog_urls = Hash.new
        Dir.entries(POSTS_PATH).each do |entry|
            next if entry == "." || entry == ".."
            path = File.join(POSTS_PATH, entry)
            f = File.open(path, "r")
            contents = ""
            f.each_line do |line|
                contents += line
            end
            f.close

            yaml_data[entry] = contents
            data = YAML.load(contents)
            date = data["date"]
            subtitle = data.fetch("subtitle", "")
            relative_dir = File.join(date.year.to_s, date.month.to_s, date.day.to_s, entry.split(".")[0])
            year = date.year.to_s
            if !@@blog_urls.key?(year)
                @@blog_urls[year] = Array.new
            end
            @@blog_urls[year].push({
                "title" => data["title"], 
                "url" => File.join(ARCHIVE, relative_dir),
                "file_path" => path,
                "date" => date,
                "subtitle" => subtitle
            })
        end

        recents_count = 3
        recents = ""
        get_recents(recents_count).reverse.each do |post|
            url = post["url"]
            title = post["title"]
            date = prettify_date(post["date"])
            subtitle = post["subtitle"]

            block = <<HERE
<div class="solutus-recents-block">
    <a class="solutus-blog-thumbnail" href="/#{url}">#{title}</a> - #{date}
    <div class="solutus-blog-subtitle"><i>#{subtitle}</i></div>
</div>
HERE
            recents += block
        end

        yaml_data.each do |entry, contents|
            path = File.join(POSTS_PATH, entry)
            data = YAML.load(contents)
            markdown_parsed = render_markdown(contents.split("---")[-1])
            data["content"] = markdown_parsed
            data["recents"] = recents
            date = data["date"]

            puts "Rendering blog post #{entry}"
            html = render_page(@@templates, settings_data, data, true)

            relative_dir = File.join(date.year.to_s, date.month.to_s, date.day.to_s, entry.split(".")[0])
            root_url = File.join(ARCHIVE, relative_dir, "index.html")

            year = date.year.to_s

            dir_path = File.join(BLOG_BUILD_PATH, relative_dir)
            if !File.directory?(dir_path)
                FileUtils.mkdir_p(dir_path)
            end
            file_path = File.join(SITE_PATH, root_url)
            puts "Created #{file_path}"
            f = File.new(file_path, "w")
            f.write(html)
            f.close()
        end    

        @@page_urls = Array.new
        relative_path_to_all_files_in_dir(SITE_PAGES_PATH).each do |entry|
            next if entry == "." || entry == ".."
            puts "Rendering page #{entry}"
            path = File.join(SITE_PAGES_PATH, entry)
            if File.file?(path)
                yml_text = ""
                f = File.open(path, "r")
                f.each_line do |line|
                    yml_text += line
                end
                data = YAML.load(yml_text)
                data["recents"] = recents
                html = render_page(@@templates, settings_data, data, false)
                new_dir = File.join(SITE_PATH, entry.split(".")[0])
                if ["error", "index"].include? entry.split(".")[0]
                    new_path = File.join(SITE_PATH, entry.split(".")[0] + ".html")
                else
                    if !File.directory?(new_dir)
                        FileUtils.mkdir_p(new_dir)
                    end
                    new_path = File.join(new_dir, "index.html")
                end
                f = File.new(new_path, "w")
                puts "Created #{new_path}"
                f.write(html)
                f.close
                @@page_urls.push({
                    "title" => data["title"],
                    "file_path" => path
                })
            end
        end    

        # Create blog archive page
        content = ""
        @@blog_urls.each do |yr, arr|
            sorted = arr.sort_by {|k| k["date"]} .reverse
            content += "<h3>#{yr}</h3>\n<hr>\n"
            sorted.each do |hash|
                url = hash["url"]
                title = hash["title"]
                pretty_date = prettify_date(hash["date"])
                content += "<div class=\"solutus-blog-block\">\n"
                content += "    <div class=\"solutus-blog-title\">\n"
                content += "        <a href=\"/#{url}\">#{title}</a>\n"
                content += "    </div>\n"
                content += "    <div>#{pretty_date}</div>\n"
                content += "</div>\n"
            end
        end
        if File.directory?(File.join(SITE_PATH, ARCHIVE))
            path = File.join(SITE_PATH, ARCHIVE, "index.html")
            data = Hash.new
            data["title"] = "Archive"
            data["content"] = content
            data["recents"] = recents
            html = render_page(@@templates, settings_data, data, false)
            f = File.new(path, "w")
            f.write(html)
            f.close
            puts "Created blog archive @ #{path}"
        end

        # Mirror build
        if settings_data["mirror"]
            dir_path = settings_data["mirror"]
            FileUtils.rm_rf("#{dir_path}/.", secure: true)
            FileUtils.copy_entry(SITE_PATH, dir_path)
        end
    end
command(*args) click to toggle source
# File lib/solutus.rb, line 547
def self.command(*args)
    start_time = Time.now
    if args.length >= 1
        if ["-v", "--version"].include?(args[0])
            puts VERSION
            return
        elsif args[0] == "deploy"
            deploy
        elsif args[0] == "build"
            build
        elsif args[0] == "serve"
            build #right now, the server won't werve unless its built.
                  #consider serializing data in cache to save time.
                  #TODO: Stress test the build for speed.
            serve
            return
        elsif args[0] == "start"
            build 
            serve
            return
        elsif args[0] == "publish"
            build
            deploy
        else 
            if args.length >= 2
                if args[0] == "password"
                    create_password(args[1])
                elsif args[0] == "create"
                    new_project(args[1])
                else
                    if args.length >= 3
                        if args[0] == "new"
                            if args[1] == "page"
                                new_page(args[2])
                            elsif args[1] == "post"
                                return new_post(args[2])
                            end
                        end
                    else
                        puts "Invalid arguments"
                        return
                    end
                end
            else
                puts "Unknown command."
                return
            end
        end
    end

    end_time = Time.now
    diff = end_time - start_time
    puts "Completed in #{diff} seconds."
end
copy_static() click to toggle source
# File lib/solutus.rb, line 875
def self.copy_static
    Dir.entries(STATIC_PATH).each do |entry|
        next if entry == "." || entry == ".."
        path = File.join(STATIC_PATH, entry)
        if File.file?(path)
            puts "Copying static file #{path}"
            FileUtils.copy(path, File.join(SITE_PATH, entry))
        elsif File.directory?(path)
            puts "Recursively copying static dir #{path}"
            FileUtils.copy_entry(path, File.join(SITE_PATH, entry))
        end
    end
end
correct_time(time) click to toggle source
# File lib/solutus.rb, line 901
def self.correct_time(time)
    time.utc.localtime("+05:30")
end
create_password(password) click to toggle source
# File lib/solutus.rb, line 935
def self.create_password(password)
    content = Digest::MD5.hexdigest password
    if File.file?(PASSWORD_FILE)
        FileUtils.rm(PASSWORD_FILE)
    end
    f = File.new(PASSWORD_FILE, "w")
    f.write(content)
    f.close
    puts "Wrote password to password.txt"
end
deploy() click to toggle source
# File lib/solutus.rb, line 602
def self.deploy
    puts "Solutus is deploying"
    system load_settings["deploy-command"]
end
get_recents(count) click to toggle source
# File lib/solutus.rb, line 893
def self.get_recents(count)
    res = Array.new
    @@blog_urls.each do |yr, arr|
        res = res + arr.sort_by {|k| k["date"]}
    end
    res.last(count)
end
load_settings() click to toggle source
# File lib/solutus.rb, line 44
def self.load_settings
    begin
        yml_text = ""
        f = File.open(SETTINGS_FILE, "r")
        f.each_line do |line|
            yml_text += line
        end
        f.close
        YAML.load(yml_text)
    rescue
        puts "Not a Solutus Project (yet) --- settings.yml not found"
        false
    end
end
new_page(title) click to toggle source
# File lib/solutus.rb, line 811
    def self.new_page(title)
        title = title.gsub(/\s+/, "").gsub(/[^a-zA-Z0-9\-]/, "").strip
        if title.empty?
            puts "Bad name"
            return
        end
        if ["edit"].include?(title)
            puts "Can't use #{title} as a page name."
            return
        end
    
        path = File.join(SITE_PAGES_PATH, title + ".yml")
        if File.file?(path)
            puts "Page with that name already exists."
            return
        end

        text = <<HERE
template: default
title: #{title}
content: |
    <h1>#{title}</h1>
    <p>
        Some Content
    </p>
HERE
        f = File.new(path, "w")
        f.write(text)
        f.close
        puts "Created new site page #{title} at #{path}"
    end
new_post(title) click to toggle source
# File lib/solutus.rb, line 843
    def self.new_post(title)
        i = 1
        new_title = title.gsub(/\s+/, "").gsub(/[^a-zA-Z0-9\-]/, "").strip
        if title.empty?
            puts "Empty title"
            return ""
        end
        while true
            path = File.join(POSTS_PATH, new_title + ".md")
            if File.file?(path)
                new_title = title + "#{i}"
                i += 1
            else
                break
            end
        end
        date = Time.now.strftime(BLOG_DATE_DATA)
        text = <<HERE
---
title: #{title}
date: #{date}
subtitle: Another new blog post
---
*This is a temporary blog post page!*
HERE
        f = File.new(path, "w")
        f.write(text)
        f.close
        puts "Created new blog post #{title} at #{path}"
        path
    end
new_project(name) click to toggle source
# File lib/solutus.rb, line 946
    def self.new_project(name)
        name = name.gsub(/[^a-zA-Z0-9\-]/, "").strip
        if name == ""
            puts "Invalid name"
            return
        end
        if File.directory?(name)
            puts "Invalid name, already a folder with that name"
            print "Overwrite? [Y/n]"
            if STDIN.gets.chomp == "Y"
                FileUtils.remove_dir(name)
            else 
                return
            end
        end
        
        Dir.mkdir(name)
        
        paths = Array.new
        ["deploy", "static", "templates", "pages", CACHE].each do |dir|
            paths.push(File.join(name, dir))
        end
        ["css", "js", "imgs"].each do |dir|
            paths.push(File.join(name, "static", dir))
        end
        ["posts", "site-pages"].each do |dir|
            paths.push(File.join(name, "pages", dir))
        end
        
        paths.each do |path|
            Dir.mkdir(path)
        end
        
        settings_file =  <<HERE
name: #{name}
author: Your Name
domain: www.yoursite.tld
deploy-command: aws s3 sync --acl public-read --sse --delete deploy/site/ s3://www.yoursite.tld
watermark: |
  <p style="text-align: center;">
      <small>Powered by <a href="https://github.com/jeromew21/solutus">Solutus</a></small>
  </p>
stylesheets: |
    <link rel="stylesheet" type="text/css" href="/css/styles.css" />
scripts: |
    <script></script>
recents-count: 3
advanced: false
HERE

        styles_file = <<HERE
body {
    background-color: #EEEEEE;
}
HERE

        default_file = <<HERE
<!DOCTYPE html>
<head>
    <title>{{title}}</title>
    {{{stylesheets}}}
</head>
<body>
    <div id="solutus-everything">
        {{#blog}}
            <h1>{{title}}</h1>
            {{{date}}}
        {{/blog}}
        {{{content}}}
        <h3>Recent Posts</h3>
        {{{recents}}}
        <small style="display: block; text-align: center;">&copy; {{year}} {{author}}</small>
        {{{watermark}}}
    </div>
    {{{scripts}}}
</body>
</html>
HERE

        datetime = Time.now.strftime(BLOG_DATE_DATA)
        test_file = <<HERE
title: Example Blog Post
date: #{datetime}
subtitle: an example post...
---
*This is an example blog post page!*
HERE

        index_file = <<HERE
template: default
title: Home page
type: html
content: |
    <h1>Hello World!</h1>
    <p>
        this is a test
    </p>
HERE

        files = {
            File.join(name, "settings.yml") => settings_file,
            File.join(name, ".gitignore") => PASSWORD_FILE + "\n",
            File.join(name, "static", "css", "styles.css") => styles_file,
            File.join(name, "templates", "default.html") => default_file,
            File.join(name, "pages", "posts", "test.md") => test_file,
            File.join(name, "pages", "site-pages", "index.yml") => index_file,
        }
        
        files.each do |filename, contents|
            puts "Created #{filename}"
            f = File.new(filename, "w")
            f.write(contents)
            f.close
        end

        puts "Created new project #{name}"
    end
page_urls() click to toggle source
# File lib/solutus.rb, line 539
def self.page_urls
    @@page_urls
end
prettify_date(date) click to toggle source
# File lib/solutus.rb, line 889
def self.prettify_date(date)
    date.strftime("%b %e") + DAY_ENDINGS[date.strftime("%e").to_i]
end
relative_path_to_all_files_in_dir(dir) click to toggle source
# File lib/solutus.rb, line 920
def self.relative_path_to_all_files_in_dir(dir)
    result = Array.new
    Dir.entries(dir).each do |entry|
        next if entry == "." || entry == ".."
        path = File.join(dir, entry)
        if File.file?(path)
            result.push(entry)
        elsif File.directory?(path)
            result = result + relative_path_to_all_files_in_dir(path).collect {|x| File.join(entry, x)}
        end
    end
    result
end
render_markdown(markdown) click to toggle source
# File lib/solutus.rb, line 905
def self.render_markdown(markdown)
    Redcarpet::Markdown.new(Redcarpet::Render::HTML.new).render(markdown)
end
render_page(templates, default_data, data, blog=false) click to toggle source
# File lib/solutus.rb, line 789
def self.render_page(templates, default_data, data, blog=false)
    if data.key?("template")
        SolutusStache.template = templates[data["template"]]
    else
        SolutusStache.template = templates[DEFAULT_TEMPLATE]
    end
    result = SolutusStache.new
    default_data.each do |key, val|
        result[key.to_sym] = val || ""
    end
    data.each do |key, val|
        next if key == "template"
        if key != "title"
            val = wrap_element(key, val)
        end
        result[key.to_sym] = val || ""
    end
    result[:blog] = blog || false
    result[:year] = Time.now.year
    result.render
end
serve() click to toggle source
# File lib/solutus.rb, line 607
def self.serve
    puts "Solutus is serving"
    puts "View at http://localhost:4567/"
    Solutus_Server.run!
end
templates() click to toggle source
# File lib/solutus.rb, line 543
def self.templates
    @@templates
end
wrap_element(key, content) click to toggle source
# File lib/solutus.rb, line 909
def self.wrap_element(key, content)
    if content.is_a?(Time)
        content = correct_time(content).strftime(BLOG_DATE_FORMAT)
    end
    result = ""
    result += "<div id=\"solutus-#{key}\">\n"
    result += content
    result += "</div>"
    result
end