aardi 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/lib/aardi/abstract_blog.rb +27 -0
  3. data/lib/aardi/abstract_feed.rb +32 -0
  4. data/lib/aardi/abstract_page_support.rb +20 -0
  5. data/lib/aardi/archive.rb +48 -0
  6. data/lib/aardi/atom_feed.rb +58 -0
  7. data/lib/aardi/blog.rb +50 -0
  8. data/lib/aardi/config.rb +21 -0
  9. data/lib/aardi/content.rb +17 -0
  10. data/lib/aardi/content_hashes.rb +39 -0
  11. data/lib/aardi/custom_renderer.rb +43 -0
  12. data/lib/aardi/day.rb +44 -0
  13. data/lib/aardi/file_target.rb +43 -0
  14. data/lib/aardi/folder.rb +41 -0
  15. data/lib/aardi/home.rb +51 -0
  16. data/lib/aardi/init_files/config.yml +26 -0
  17. data/lib/aardi/json_feed.rb +32 -0
  18. data/lib/aardi/ledger.rb +15 -0
  19. data/lib/aardi/month.rb +48 -0
  20. data/lib/aardi/orphanage.rb +15 -0
  21. data/lib/aardi/page.rb +25 -0
  22. data/lib/aardi/page_content.rb +21 -0
  23. data/lib/aardi/page_target.rb +16 -0
  24. data/lib/aardi/path_servlet.rb +18 -0
  25. data/lib/aardi/post.rb +56 -0
  26. data/lib/aardi/site.rb +68 -0
  27. data/lib/aardi/sitemap.rb +56 -0
  28. data/lib/aardi/tasks/fixtimes.rake +8 -0
  29. data/lib/aardi/tasks/homepage.rake +6 -0
  30. data/lib/aardi/tasks/init.rake +34 -0
  31. data/lib/aardi/tasks/load_config.rake +7 -0
  32. data/lib/aardi/tasks/new.rake +16 -0
  33. data/lib/aardi/tasks/now.rake +6 -0
  34. data/lib/aardi/tasks/recent.rake +6 -0
  35. data/lib/aardi/tasks/render.rake +6 -0
  36. data/lib/aardi/tasks/server.rake +18 -0
  37. data/lib/aardi/tasks.rb +5 -0
  38. data/lib/aardi/template.rb +41 -0
  39. data/lib/aardi/timekeeper.rb +53 -0
  40. data/lib/aardi/version.rb +5 -0
  41. data/lib/aardi/year.rb +46 -0
  42. data/lib/aardi.rb +54 -0
  43. metadata +274 -0
data/lib/aardi/page.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class Page
5
+ include AbstractPageSupport
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ parse_source path
10
+ end
11
+
12
+ def content
13
+ @src_content
14
+ end
15
+
16
+ def render
17
+ page_content = PageContent.new(content, title, metadata)
18
+ PageTarget.new(page_content, target_path).write
19
+ end
20
+
21
+ def target_path
22
+ @path.pathmap("%X.html")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class PageContent < Content
5
+ attr_reader :title, :metadata
6
+
7
+ def initialize(src_content, title, metadata = {})
8
+ super(src_content)
9
+ @title = title
10
+ @metadata = metadata
11
+ end
12
+
13
+ def content
14
+ @src_content
15
+ end
16
+
17
+ def output
18
+ @output ||= Aardi.ledger[:template].render(self)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class PageTarget < FileTarget
5
+ def write
6
+ super
7
+ Aardi.ledger[:html_files].delete(@path)
8
+ end
9
+
10
+ private
11
+
12
+ def file_exists?
13
+ Aardi.ledger[:html_files].include? @path
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webrick"
4
+
5
+ module Aardi
6
+ class PathServlet < WEBrick::HTTPServlet::AbstractServlet
7
+ def service(req, res)
8
+ # use index if folder
9
+ path = "./#{req.path}".sub(%r{/$}, "/index.html")
10
+
11
+ file = path if File.exist?(path)
12
+ file ||= "#{path}.html" if File.exist?("#{path}.html")
13
+ raise(WEBrick::HTTPStatus::NotFound, "#{path} not found.") unless file
14
+
15
+ res.body = File.read(file)
16
+ end
17
+ end
18
+ end
data/lib/aardi/post.rb ADDED
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class Post < AbstractBlog
5
+ include AbstractPageSupport
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ parse_source path
10
+ end
11
+
12
+ def content
13
+ "#{@src_content}\n<div><span class=\"bookmark\">[<a href=\"#{url}\">bookmark</a>]</span></div>\n"
14
+ end
15
+
16
+ def creation = metadata["Creation"]
17
+
18
+ def day = creation.day
19
+
20
+ def feed_snippet
21
+ clean_content = @src_content.sub(/\A(### .*\n)?\n+/, "")
22
+ render_markup(clean_content).strip
23
+ end
24
+
25
+ def month = creation.month
26
+
27
+ def name = File.basename(@path, ".*")
28
+
29
+ def report_field_summary
30
+ creation_header = creation.strftime("%e %b %Y")
31
+ puts "#{creation_header} | #{@path} | #{title}"
32
+ end
33
+
34
+ def target_path
35
+ "./#{short_target}.html"
36
+ end
37
+
38
+ def updated = metadata["Updated"] || creation
39
+
40
+ def url = "#{Aardi.config[:site_url]}/#{short_target}"
41
+
42
+ def year = creation.year
43
+
44
+ private
45
+
46
+ def render_markup(content)
47
+ ledger = Aardi.ledger
48
+ ledger[:custom_renderer].reset
49
+ ledger[:markdown_renderer].render(content)
50
+ end
51
+
52
+ def short_target
53
+ "#{Aardi.config[:blog_archive_path]}/#{creation.strftime("%Y/%m/%d")}/#{name}"
54
+ end
55
+ end
56
+ end
data/lib/aardi/site.rb ADDED
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class Site < AbstractBlog
5
+ def initialize
6
+ initialize_ledger
7
+ end
8
+
9
+ # :reek:FeatureEnvy
10
+ def blog
11
+ aardi_config = Aardi.config
12
+ @blog ||= Blog.new(aardi_config[:blog_posts_path], aardi_config[:blog_archive_path])
13
+ end
14
+
15
+ def render
16
+ super
17
+ content_hashes.write
18
+ warn_about_orphans
19
+ end
20
+
21
+ private
22
+
23
+ def children
24
+ [Folder.new("."), blog, sitemap]
25
+ end
26
+
27
+ def content_hashes
28
+ @content_hashes ||= ContentHashes.new(Aardi.config[:content_hashes_path])
29
+ end
30
+
31
+ def custom_renderer
32
+ @custom_renderer ||= CustomRenderer.new
33
+ end
34
+
35
+ def html_files
36
+ @html_files ||= Dir.glob("./**/*.html").to_set
37
+ end
38
+
39
+ def initialize_ledger
40
+ ledger = Aardi.ledger
41
+ # set up content hashes so they're in place while building out the rest
42
+ ledger[:content_hashes] = content_hashes
43
+
44
+ {custom_renderer:, markdown_renderer:, html_files:, sitemap:, template:}.each do |message, value|
45
+ ledger[message] = value
46
+ end
47
+ end
48
+
49
+ def markdown_renderer
50
+ Redcarpet::Markdown.new(custom_renderer, Aardi.config[:markup_options])
51
+ end
52
+
53
+ def sitemap
54
+ @sitemap ||= Sitemap.new
55
+ end
56
+
57
+ def template
58
+ Template.new Aardi.config[:template_path]
59
+ end
60
+
61
+ def warn_about_orphans
62
+ Orphanage.new.warn
63
+ end
64
+
65
+ def write_target
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class Sitemap
5
+ # :reek:NestedIterators
6
+ def content
7
+ sitemap = Nokogiri::XML::Builder.new(encoding: "UTF-8") do
8
+ urlset(xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9") do |urlset|
9
+ urls.each do |path, details|
10
+ url_details(path, details, urlset)
11
+ end
12
+ end
13
+ end
14
+
15
+ sitemap.to_xml
16
+ end
17
+
18
+ def render
19
+ source = Content.new(content)
20
+ FileTarget.new(source, target_path).write
21
+ end
22
+
23
+ def target_path = "./sitemap.xml"
24
+
25
+ def update_mtime(path, path_mtime)
26
+ urls[path][:lastmod] = path_mtime.iso8601
27
+ end
28
+
29
+ # Methods called inside builder blocks must be public.
30
+ # :reek:FeatureEnvy
31
+ def url_details(path, details, urlset)
32
+ missing_path(path) unless File.exist?("./#{path}")
33
+
34
+ urlset.url do
35
+ details.slice(:loc, :lastmod, :changefreq).each do |node, value|
36
+ urlset.public_send node, value
37
+ end
38
+ end
39
+ end
40
+
41
+ def urls
42
+ @urls ||= Aardi.config[:sitemap_entries].to_h { |path, cf| [path, url_values(path, cf)] }
43
+ end
44
+
45
+ private
46
+
47
+ def missing_path(path)
48
+ puts("FATAL: #{path} missing")
49
+ exit
50
+ end
51
+
52
+ def url_values(path, changefreq)
53
+ {loc: "#{Aardi.config[:site_url]}#{path}", changefreq:}
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc("Fix file timestamps")
4
+ task(:fixtimes) do
5
+ require "aardi/timekeeper"
6
+
7
+ Aardi::Timekeeper.new.run
8
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc("Visit home page")
4
+ task homepage: [:load_config] do
5
+ system("open #{Aardi.config[:site_url]}")
6
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ INIT_FILES_DIR = File.expand_path("../init_files", __dir__)
4
+
5
+ desc("Scaffold a new Aardi site")
6
+ task :init do
7
+ Dir.glob("#{INIT_FILES_DIR}/**/*", File::FNM_DOTMATCH).each do |src|
8
+ next if File.directory?(src)
9
+
10
+ dest = File.basename(src)
11
+
12
+ unless File.exist?(dest)
13
+ FileUtils.cp(src, dest)
14
+ puts "Created #{dest}"
15
+ next
16
+ end
17
+
18
+ loop do
19
+ print "#{dest} already exists. Overwrite? [y]es / [n]o: "
20
+ answer = $stdin.gets&.strip&.downcase
21
+ case answer
22
+ when "y"
23
+ FileUtils.cp(src, dest)
24
+ puts "Overwrote #{dest}"
25
+ break
26
+ when "n"
27
+ puts "Skipped #{dest}"
28
+ break
29
+ end
30
+ end
31
+ end
32
+
33
+ puts "Site scaffolding installed."
34
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Rake/Desc
4
+ task :load_config do
5
+ Aardi.config.load "./config.yml"
6
+ end
7
+ # rubocop:enable Rake/Desc
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ def create_new_post
4
+ now = Time.now.utc
5
+ new_post_file = "#{now.to_i}.md"
6
+ new_post_path = "#{Aardi.config[:blog_posts_path]}/#{new_post_file.hash.modulo(36).to_s(36)}/#{new_post_file}"
7
+ new_post_content = "Creation: #{now.iso8601}\n\n----\n### title\n\n[source](url): \"excerpt\"\n"
8
+ FileUtils.mkdir_p(File.dirname(new_post_path))
9
+ File.write(new_post_path, new_post_content)
10
+ puts(new_post_path)
11
+ end
12
+
13
+ desc("Create a new blog post and reveal it in Finder")
14
+ task new: [:load_config] do
15
+ create_new_post
16
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc("Produce an Updated header with current timestamp")
4
+ task :now do
5
+ puts "Updated: #{Time.now.utc.iso8601}"
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc("View recent posts")
4
+ task recent: [:load_config] do
5
+ Aardi::Site.new.blog.report_recent
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc("Render new or updated files [DEFAULT]")
4
+ task render: [:load_config] do
5
+ Aardi::Site.new.render
6
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc("Run a server")
4
+ task :server do
5
+ require "aardi/path_servlet"
6
+
7
+ port = 8000
8
+ url = "http://localhost:#{port}/"
9
+ root = "."
10
+ puts(url)
11
+
12
+ server = WEBrick::HTTPServer.new(Port: port, DocumentRoot: root)
13
+ server.mount("/", Aardi::PathServlet)
14
+
15
+ trap("INT") { server.shutdown }
16
+ system("(open '#{url}')&")
17
+ server.start
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aardi"
4
+
5
+ Dir[File.join(__dir__, "tasks", "*.rake")].each { |f| load f }
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ # :reek:DataClump
5
+ class Template
6
+ def initialize(path)
7
+ @path = path
8
+ @content = File.read(path).strip
9
+ @dom = Nokogiri::HTML5.parse(@content)
10
+ end
11
+
12
+ # :reek:TooManyStatements
13
+ def render(src)
14
+ Aardi.ledger[:custom_renderer].reset
15
+ dom = @dom.clone
16
+
17
+ add_main(dom, src)
18
+ add_title(dom, src)
19
+ add_description(dom, src)
20
+
21
+ dom.to_html.strip
22
+ end
23
+
24
+ private
25
+
26
+ def add_description(dom, src)
27
+ description = src.metadata["Description"]
28
+ return unless description
29
+
30
+ dom.at_css('meta[name="description"]')["content"] = description
31
+ end
32
+
33
+ def add_main(dom, src)
34
+ dom.at_css("main").add_child(Aardi.ledger[:markdown_renderer].render(src.content))
35
+ end
36
+
37
+ def add_title(dom, src)
38
+ dom.at_css("title").content += " #{src.title}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "git"
4
+
5
+ module Aardi
6
+ # :reek:TooManyInstanceVariables
7
+ class Timekeeper
8
+ def initialize
9
+ @repo = Git.open(Dir.pwd)
10
+ @commit_log = @repo.log(:all).all
11
+ @files = tracked_files_by_mtime
12
+ @files_count = @files.count
13
+ @updated = 0
14
+ @prior_summary_length = 0
15
+ end
16
+
17
+ def run
18
+ @files.each.with_index do |path, index|
19
+ @prior_summary_length = print_progress(index, path)
20
+
21
+ commit_date = author_date(path)
22
+ next unless commit_date
23
+
24
+ @updated += 1 if fix_mtime?(path, commit_date)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def author_date(path)
31
+ @commit_log.object(path).execute.first&.author_date
32
+ end
33
+
34
+ def fix_mtime?(path, author_date)
35
+ mtime = File.mtime(path)
36
+ return false if author_date == mtime
37
+
38
+ FileUtils.touch(path, mtime: author_date)
39
+ puts "\n #{mtime} -> #{author_date}"
40
+ true
41
+ end
42
+
43
+ def print_progress(index, path)
44
+ summary = "(#{index} / #{@files_count}) #{@updated}: #{path}"
45
+ print format("\r%-#{@prior_summary_length}s", summary)
46
+ summary.length
47
+ end
48
+
49
+ def tracked_files_by_mtime
50
+ @repo.ls_files.keys.sort_by { |file| File.mtime(file) }.reverse!
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ VERSION = "0.9.0"
5
+ end
data/lib/aardi/year.rb ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class Year < AbstractBlog
5
+ def initialize(key, archive_path)
6
+ @key = key
7
+ @archive_path = archive_path
8
+ @index = Hash.new { |hash, month| hash[month] = Month.new(self, month, archive_path) }
9
+ end
10
+
11
+ def <<(post)
12
+ @index[post.month] << post
13
+ end
14
+
15
+ def archive_row(year_fmt, month_fmt)
16
+ months_map = (1..12).map { |month| @index[month].archive_cell(month_fmt) }.join
17
+ format(year_fmt, year: to_s, months: months_map)
18
+ end
19
+
20
+ def content
21
+ "# #{title}\n#{months.map { |month| month_link(month) }.join("\n")}"
22
+ end
23
+
24
+ def target_path
25
+ "./#{@archive_path}/#{self}/index.html"
26
+ end
27
+
28
+ def title = to_s
29
+
30
+ private
31
+
32
+ def children
33
+ months
34
+ end
35
+
36
+ def month_link(month)
37
+ "- [#{Date::MONTHNAMES[month.key]}](#{Aardi.config[:site_url]}/#{@archive_path}/#{self}/#{month}/)"
38
+ end
39
+
40
+ def months
41
+ @months ||= @index.values.sort_by { |value| -value.key }
42
+ end
43
+
44
+ def to_s = key.to_s
45
+ end
46
+ end
data/lib/aardi.rb ADDED
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "fileutils"
5
+ require "json"
6
+ require "yaml"
7
+ require "zlib"
8
+
9
+ require "nokogiri"
10
+ require "rake"
11
+ require "redcarpet"
12
+
13
+ require_relative "aardi/version"
14
+ require_relative "aardi/config"
15
+ require_relative "aardi/ledger"
16
+ require_relative "aardi/content"
17
+ require_relative "aardi/content_hashes"
18
+ require_relative "aardi/custom_renderer"
19
+ require_relative "aardi/abstract_blog"
20
+ require_relative "aardi/abstract_page_support"
21
+ require_relative "aardi/abstract_feed"
22
+ require_relative "aardi/file_target"
23
+ require_relative "aardi/page_target"
24
+ require_relative "aardi/page_content"
25
+ require_relative "aardi/template"
26
+ require_relative "aardi/page"
27
+ require_relative "aardi/post"
28
+ require_relative "aardi/day"
29
+ require_relative "aardi/month"
30
+ require_relative "aardi/year"
31
+ require_relative "aardi/archive"
32
+ require_relative "aardi/home"
33
+ require_relative "aardi/atom_feed"
34
+ require_relative "aardi/json_feed"
35
+ require_relative "aardi/blog"
36
+ require_relative "aardi/folder"
37
+ require_relative "aardi/sitemap"
38
+ require_relative "aardi/orphanage"
39
+ require_relative "aardi/site"
40
+
41
+ RubyVM::YJIT.enable if RUBY_ENGINE == "ruby" && RUBY_VERSION.to_f >= 3.1
42
+
43
+ module Aardi
44
+ class << self
45
+ def config = @config ||= Config.new
46
+
47
+ def ledger = @ledger ||= Ledger.new
48
+
49
+ def reset!
50
+ @config = Config.new
51
+ @ledger = Ledger.new
52
+ end
53
+ end
54
+ end