aardi 1.0.0 → 2.0.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 +4 -4
  2. data/lib/aardi/abstract_blog.rb +9 -4
  3. data/lib/aardi/abstract_feed.rb +15 -3
  4. data/lib/aardi/abstract_page_support.rb +9 -3
  5. data/lib/aardi/archive.rb +24 -19
  6. data/lib/aardi/atom_feed.rb +2 -4
  7. data/lib/aardi/blog.rb +18 -12
  8. data/lib/aardi/config.rb +15 -16
  9. data/lib/aardi/content_hashes.rb +11 -4
  10. data/lib/aardi/custom_renderer.rb +1 -3
  11. data/lib/aardi/day.rb +4 -3
  12. data/lib/aardi/errors.rb +6 -0
  13. data/lib/aardi/file_target.rb +8 -9
  14. data/lib/aardi/folder.rb +5 -10
  15. data/lib/aardi/home.rb +13 -14
  16. data/lib/aardi/home_footer_links.rb +21 -0
  17. data/lib/aardi/init_files/config.yml +1 -0
  18. data/lib/aardi/json_feed.rb +2 -3
  19. data/lib/aardi/metadata.rb +36 -0
  20. data/lib/aardi/month.rb +11 -11
  21. data/lib/aardi/orphanage.rb +3 -3
  22. data/lib/aardi/page_content.rb +2 -2
  23. data/lib/aardi/page_target.rb +4 -6
  24. data/lib/aardi/post.rb +10 -20
  25. data/lib/aardi/post_bookmark_line.rb +26 -0
  26. data/lib/aardi/renderer.rb +42 -0
  27. data/lib/aardi/site.rb +11 -43
  28. data/lib/aardi/sitemap.rb +11 -5
  29. data/lib/aardi/tag_blog.rb +42 -0
  30. data/lib/aardi/tags.rb +61 -0
  31. data/lib/aardi/tasks/homepage.rake +1 -1
  32. data/lib/aardi/tasks/init.rake +3 -6
  33. data/lib/aardi/tasks/load_config.rake +1 -1
  34. data/lib/aardi/tasks/new.rake +1 -1
  35. data/lib/aardi/tasks/render.rake +2 -0
  36. data/lib/aardi/tasks/server.rake +1 -1
  37. data/lib/aardi/template.rb +33 -19
  38. data/lib/aardi/timekeeper.rb +5 -12
  39. data/lib/aardi/version.rb +1 -1
  40. data/lib/aardi/year.rb +7 -6
  41. data/lib/aardi.rb +50 -40
  42. metadata +9 -3
  43. data/lib/aardi/ledger.rb +0 -15
@@ -4,7 +4,7 @@ module Aardi
4
4
  class PageContent < Content
5
5
  attr_reader :title, :metadata
6
6
 
7
- def initialize(src_content, title, metadata = {})
7
+ def initialize(src_content, title, metadata = Metadata.new)
8
8
  super(src_content)
9
9
  @title = title
10
10
  @metadata = metadata
@@ -15,7 +15,7 @@ module Aardi
15
15
  end
16
16
 
17
17
  def output
18
- @output ||= Aardi.ledger[:template].render(self)
18
+ @output ||= Aardi.renderer.render(self)
19
19
  end
20
20
  end
21
21
  end
@@ -2,15 +2,13 @@
2
2
 
3
3
  module Aardi
4
4
  class PageTarget < FileTarget
5
- def write
6
- super
7
- Aardi.ledger[:html_files].delete(@path)
8
- end
9
-
10
5
  private
11
6
 
7
+ # Optimization: in PageTarget first check the existing .html
8
+ # list rather than always rechecking the file system. But then
9
+ # check anyway because html_files doesn't always catch tag pages.
12
10
  def file_exists?
13
- Aardi.ledger[:html_files].include? @path
11
+ @html_files.include?(@path) || File.exist?(@path)
14
12
  end
15
13
  end
16
14
  end
data/lib/aardi/post.rb CHANGED
@@ -7,24 +7,20 @@ module Aardi
7
7
  def initialize(path)
8
8
  @path = path
9
9
  parse_source path
10
- raise "#{path}: missing Creation metadata" unless metadata['Creation']
10
+
11
+ raise "#{path}: missing Creation metadata" unless metadata.creation
11
12
  end
12
13
 
13
14
  def content
14
- "#{@src_content}\n<div><span class=\"bookmark\">[<a href=\"#{url}\">bookmark</a>]</span></div>\n"
15
+ @content ||= "#{@src_content}\n<div>#{PostBookmarkLine.new(self)}</div>\n"
15
16
  end
16
17
 
17
- def creation = metadata['Creation']
18
-
19
- def day = creation.day
18
+ def creation = metadata.creation
20
19
 
21
20
  def feed_snippet
22
- clean_content = @src_content.sub(/\A(### .*\n)?\n+/, '')
23
- render_markup(clean_content).strip
21
+ @feed_snippet ||= Aardi.renderer.markup_feed_snippet(@src_content)
24
22
  end
25
23
 
26
- def month = creation.month
27
-
28
24
  def name = File.basename(@path, '.*')
29
25
 
30
26
  def report_field_summary
@@ -32,26 +28,20 @@ module Aardi
32
28
  puts "#{creation_header} | #{@path} | #{title}"
33
29
  end
34
30
 
31
+ def tags = metadata.tags
32
+
35
33
  def target_path
36
34
  "./#{short_target}.html"
37
35
  end
38
36
 
39
- def updated = metadata['Updated'] || creation
37
+ def updated = metadata.updated || creation
40
38
 
41
- def url = "#{Aardi.config[:site_url]}/#{short_target}"
42
-
43
- def year = creation.year
39
+ def url = "#{Config[:site_url]}/#{short_target}"
44
40
 
45
41
  private
46
42
 
47
- def render_markup(content)
48
- ledger = Aardi.ledger
49
- ledger[:custom_renderer].reset
50
- ledger[:markdown_renderer].render(content)
51
- end
52
-
53
43
  def short_target
54
- "#{Aardi.config[:blog_archive_path]}/#{creation.strftime('%Y/%m/%d')}/#{name}"
44
+ "#{Config[:blog_archive_path]}/#{creation.strftime('%Y/%m/%d')}/#{name}"
55
45
  end
56
46
  end
57
47
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class PostBookmarkLine
5
+ def initialize(post)
6
+ @post = post
7
+ end
8
+
9
+ def to_s
10
+ %(<span class="bookmark">[<a href="#{@post.url}">bookmark</a>]#{tag_links}</span>)
11
+ end
12
+
13
+ private
14
+
15
+ def tag_links
16
+ tags = @post.tags
17
+ return '' unless tags&.any?
18
+
19
+ " #{tags.map { |tag| %(<a href="#{tags_base_url}/#{tag}/">#{tag}</a>) }.join(', ')}"
20
+ end
21
+
22
+ def tags_base_url
23
+ "#{Config[:site_url]}/#{Config[:blog_tags_path]}"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ # :reek:TooManyInstanceVariables
5
+ class Renderer
6
+ attr_reader :content_hashes, :html_files, :sitemap
7
+
8
+ # :reek:ControlParameter
9
+ def initialize(html_files: nil, content_hashes: nil, sitemap: nil)
10
+ @html_files = html_files || Dir.glob('./**/*.html').to_set
11
+ @content_hashes = content_hashes || ContentHashes.new(Config[:content_hashes_path])
12
+ @sitemap = sitemap || Sitemap.new
13
+ @template = Template.new(Config[:template_path])
14
+ @custom_renderer = CustomRenderer.new
15
+ @markup_renderer = Redcarpet::Markdown.new(@custom_renderer, markup_options)
16
+ end
17
+
18
+ def finalize(result)
19
+ content_hashes.save(result)
20
+ Orphanage.new.report(html_files, result.keys)
21
+ end
22
+
23
+ def markup(content)
24
+ @custom_renderer.reset
25
+ @markup_renderer.render(content)
26
+ end
27
+
28
+ def markup_feed_snippet(content)
29
+ markup(content.sub(/\A(### .*\n)?\n+/, '')).strip
30
+ end
31
+
32
+ def render(src)
33
+ @template.render(src)
34
+ end
35
+
36
+ private
37
+
38
+ def markup_options
39
+ (Config.fetch(:markup_options) || {}).transform_keys(&:to_sym)
40
+ end
41
+ end
42
+ end
data/lib/aardi/site.rb CHANGED
@@ -3,65 +3,33 @@
3
3
  module Aardi
4
4
  class Site < AbstractBlog
5
5
  def initialize
6
- initialize_ledger
6
+ posts.each do |post|
7
+ blog << post
8
+ end
7
9
  end
8
10
 
9
- # :reek:FeatureEnvy
10
11
  def blog
11
- aardi_config = Aardi.config
12
- @blog ||= Blog.new(aardi_config[:blog_posts_path], aardi_config[:blog_archive_path])
12
+ @blog ||= Blog.new
13
13
  end
14
14
 
15
15
  def render
16
- super
17
- content_hashes.write
18
- warn_about_orphans
16
+ Aardi.renderer.finalize(super)
19
17
  end
20
18
 
21
19
  private
22
20
 
23
21
  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
22
+ [Folder.new('.'), blog, Aardi.renderer.sitemap]
55
23
  end
56
24
 
57
- def template
58
- Template.new Aardi.config[:template_path]
25
+ def post_paths
26
+ Dir.glob("#{Config[:blog_posts_path]}/**/*.md")
59
27
  end
60
28
 
61
- def warn_about_orphans
62
- Orphanage.new.report
29
+ def posts
30
+ post_paths.map { |path| Post.new(path) }
63
31
  end
64
32
 
65
- def write_target; end
33
+ def write_target = {}
66
34
  end
67
35
  end
data/lib/aardi/sitemap.rb CHANGED
@@ -15,6 +15,12 @@ module Aardi
15
15
  sitemap.to_xml
16
16
  end
17
17
 
18
+ def record_mtime(path, path_mtime)
19
+ return unless urls.key?(path) && path_mtime
20
+
21
+ update_mtime(path, path_mtime)
22
+ end
23
+
18
24
  def render
19
25
  source = Content.new(content)
20
26
  FileTarget.new(source, target_path).write
@@ -26,7 +32,8 @@ module Aardi
26
32
  urls[path][:lastmod] = path_mtime.iso8601
27
33
  end
28
34
 
29
- # Methods called inside builder blocks must be public.
35
+ # Absent a block variable on Nokogiri::XML::Builder.new, this
36
+ # is called by the builder, not sitemap itself, and must be public.
30
37
  # :reek:FeatureEnvy
31
38
  def url_details(path, details, urlset)
32
39
  missing_path(path) unless File.exist?("./#{path}")
@@ -39,18 +46,17 @@ module Aardi
39
46
  end
40
47
 
41
48
  def urls
42
- @urls ||= Aardi.config[:sitemap_entries].to_h { |path, cf| [path, url_values(path, cf)] }
49
+ @urls ||= Config[:sitemap_entries].to_h { |path, cf| [path, url_values(path, cf)] }
43
50
  end
44
51
 
45
52
  private
46
53
 
47
54
  def missing_path(path)
48
- puts("FATAL: #{path} missing")
49
- exit
55
+ raise MissingPathError, "#{path} is missing"
50
56
  end
51
57
 
52
58
  def url_values(path, changefreq)
53
- { loc: "#{Aardi.config[:site_url]}#{path}", changefreq: }
59
+ { loc: "#{Config[:site_url]}#{path}", changefreq: }
54
60
  end
55
61
  end
56
62
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class TagBlog < Blog
5
+ # :reek:DuplicateMethodCall
6
+ def initialize(tag)
7
+ super()
8
+ @tag = tag
9
+ @blog_path = "#{Config[:blog_tags_path]}/#{tag}"
10
+ @archive_path = "#{@blog_path}/#{Config[:blog_archive_path]}"
11
+ end
12
+
13
+ def <<(post)
14
+ @posts << post
15
+ archive << post
16
+ end
17
+
18
+ def count
19
+ @posts.count
20
+ end
21
+
22
+ def index_line
23
+ "- #{inline_link_text}"
24
+ end
25
+
26
+ def inline_link_text
27
+ "[#{tag}](#{url}) (#{count})"
28
+ end
29
+
30
+ def url
31
+ "#{Config[:site_url]}/#{@blog_path}/"
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :tag
37
+
38
+ def children
39
+ [archive, home, atom_feed, json_feed]
40
+ end
41
+ end
42
+ end
data/lib/aardi/tags.rb ADDED
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aardi
4
+ class Tags < AbstractBlog
5
+ def initialize
6
+ @index = Hash.new { |hash, tag| hash[tag] = TagBlog.new(tag) }
7
+ end
8
+
9
+ def <<(post)
10
+ post.tags&.each do |tag|
11
+ @index[tag] << post
12
+ end
13
+ end
14
+
15
+ def content
16
+ return "# Tags\n" if counts.empty?
17
+
18
+ "# Tags\n\n#{tag_lines.join("\n")}\n"
19
+ end
20
+
21
+ def empty? = @index.empty?
22
+
23
+ def inline_links
24
+ counts.map { |_tag, tag_blog| tag_blog.inline_link_text }.join(', ')
25
+ end
26
+
27
+ def target_path
28
+ "./#{tags_base_path}/index.html"
29
+ end
30
+
31
+ def title = 'Tags'
32
+
33
+ private
34
+
35
+ def children
36
+ @index.values
37
+ end
38
+
39
+ def counts
40
+ sorted_index
41
+ end
42
+
43
+ def sorted_index
44
+ @sorted_index ||= @index.sort_by { |_key, tag_blog| tag_blog.count }.reverse.to_h
45
+ end
46
+
47
+ def tag_lines
48
+ sorted_index.map { |_tag, tag_blog| tag_blog.index_line }
49
+ end
50
+
51
+ def tags_base_path
52
+ Config[:blog_tags_path]
53
+ end
54
+
55
+ def write_target
56
+ return {} if empty?
57
+
58
+ super
59
+ end
60
+ end
61
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  desc('Visit home page')
4
4
  task homepage: [:load_config] do
5
- system("open #{Aardi.config[:site_url]}")
5
+ system('open', Aardi::Config[:site_url])
6
6
  end
@@ -6,13 +6,10 @@ unless defined?(InitTask)
6
6
  module InitTask
7
7
  def self.install_file(src)
8
8
  dest = File.basename(src)
9
+ return puts "Skipped #{dest}" if File.exist?(dest) && !prompt_overwrite?(dest)
9
10
 
10
- if !File.exist?(dest) || prompt_overwrite?(dest)
11
- FileUtils.cp(src, dest)
12
- puts "Wrote #{dest}"
13
- else
14
- puts "Skipped #{dest}"
15
- end
11
+ FileUtils.cp(src, dest)
12
+ puts "Wrote #{dest}"
16
13
  end
17
14
 
18
15
  def self.prompt_overwrite?(filename)
@@ -2,6 +2,6 @@
2
2
 
3
3
  # rubocop:disable Rake/Desc
4
4
  task :load_config do
5
- Aardi.config.load './config.yml'
5
+ Aardi::Config.load './config.yml'
6
6
  end
7
7
  # rubocop:enable Rake/Desc
@@ -3,7 +3,7 @@
3
3
  def create_new_post
4
4
  now = Time.now.utc
5
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}"
6
+ new_post_path = "#{Aardi::Config[:blog_posts_path]}/#{new_post_file.hash.modulo(36).to_s(36)}/#{new_post_file}"
7
7
  new_post_content = "Creation: #{now.iso8601}\n\n----\n### title\n\n[source](url): \"excerpt\"\n"
8
8
  FileUtils.mkdir_p(File.dirname(new_post_path))
9
9
  File.write(new_post_path, new_post_content)
@@ -3,4 +3,6 @@
3
3
  desc('Render new or updated files [DEFAULT]')
4
4
  task render: [:load_config] do
5
5
  Aardi::Site.new.render
6
+ rescue Aardi::MissingPathError => e
7
+ abort e.message
6
8
  end
@@ -13,6 +13,6 @@ task :server do
13
13
  server.mount('/', Aardi::PathServlet)
14
14
 
15
15
  trap('INT') { server.shutdown }
16
- system("(open '#{url}')&")
16
+ system('open', url)
17
17
  server.start
18
18
  end
@@ -3,39 +3,53 @@
3
3
  module Aardi
4
4
  # :reek:DataClump
5
5
  class Template
6
+ PLACEHOLDER_MAIN = '__PLACEHOLDER_MAIN__'
7
+ PLACEHOLDER_TITLE = '__PLACEHOLDER_TITLE__'
8
+ PLACEHOLDER_DESCRIPTION = '__PLACEHOLDER_DESCRIPTION__'
9
+
6
10
  def initialize(path)
7
11
  @path = path
8
- @content = File.read(path).strip
9
- @dom = Nokogiri::HTML5.parse(@content)
12
+ dom = Nokogiri::HTML5.parse(File.read(path).strip)
13
+ required(dom, 'main').add_child(PLACEHOLDER_MAIN)
14
+ required(dom, 'title').content += PLACEHOLDER_TITLE
15
+ update_description_if_present(dom.at_css('meta[name="description"]'))
16
+ @compiled = dom.to_html.strip
10
17
  end
11
18
 
12
- # :reek:TooManyStatements
13
19
  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
20
+ result = @compiled.dup
21
+ result.sub!(PLACEHOLDER_MAIN, Aardi.renderer.markup(src.content))
22
+ result.sub!(PLACEHOLDER_TITLE, " #{text_escape(src.title)}")
23
+ result.sub!(PLACEHOLDER_DESCRIPTION, description_value(src)) if @default_description
24
+ result
22
25
  end
23
26
 
24
27
  private
25
28
 
26
- def add_description(dom, src)
27
- description = src.metadata['Description']
28
- return unless description
29
+ def description_value(src)
30
+ escape_attributes(src.metadata.description || @default_description)
31
+ end
29
32
 
30
- dom.at_css('meta[name="description"]')['content'] = description
33
+ def escape_attributes(str)
34
+ str.gsub('&', '&amp;').gsub('"', '&quot;')
31
35
  end
32
36
 
33
- def add_main(dom, src)
34
- dom.at_css('main').add_child(Aardi.ledger[:markdown_renderer].render(src.content))
37
+ def required(dom, selector)
38
+ dom.at_css(selector) ||
39
+ raise(MissingTemplateElementError,
40
+ "Template missing required <#{selector}> element in #{@path}")
35
41
  end
36
42
 
37
- def add_title(dom, src)
38
- dom.at_css('title').content += " #{src.title}"
43
+ def text_escape(str)
44
+ str.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
45
+ end
46
+
47
+ # :reek:FeatureEnvy
48
+ def update_description_if_present(meta)
49
+ return unless meta
50
+
51
+ @default_description = meta['content'] || ''
52
+ meta['content'] = PLACEHOLDER_DESCRIPTION
39
53
  end
40
54
  end
41
55
  end
@@ -3,21 +3,18 @@
3
3
  require 'git'
4
4
 
5
5
  module Aardi
6
- # :reek:TooManyInstanceVariables
7
6
  class Timekeeper
8
7
  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
8
+ repo = Git.open(Dir.pwd)
9
+ @commit_log = repo.log(:all).all
10
+ @files = repo.ls_files.keys.sort_by { |file| File.mtime(file) }.reverse!
13
11
  @updated = 0
14
12
  @prior_summary_length = 0
15
13
  end
16
14
 
17
15
  def run
18
- @files.each.with_index do |path, index|
16
+ @files.each_with_index do |path, index|
19
17
  @prior_summary_length = print_progress(index, path)
20
-
21
18
  commit_date = author_date(path)
22
19
  next unless commit_date
23
20
 
@@ -41,13 +38,9 @@ module Aardi
41
38
  end
42
39
 
43
40
  def print_progress(index, path)
44
- summary = "(#{index} / #{@files_count}) #{@updated}: #{path}"
41
+ summary = "(#{index} / #{@files.size}) #{@updated}: #{path}"
45
42
  print format("\r%-#{@prior_summary_length}s", summary)
46
43
  summary.length
47
44
  end
48
-
49
- def tracked_files_by_mtime
50
- @repo.ls_files.keys.sort_by { |file| File.mtime(file) }.reverse!
51
- end
52
45
  end
53
46
  end
data/lib/aardi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aardi
4
- VERSION = '1.0.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/aardi/year.rb CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  module Aardi
4
4
  class Year < AbstractBlog
5
- def initialize(key, archive_path)
5
+ def initialize(key, archive_path, tag = nil)
6
6
  @key = key
7
7
  @archive_path = archive_path
8
- @index = Hash.new { |hash, month| hash[month] = Month.new(self, month, archive_path) }
8
+ @index = Hash.new { |hash, month| hash[month] = Month.new(self, month, archive_path, tag) }
9
+ @tag = tag
9
10
  end
10
11
 
11
12
  def <<(post)
12
- @index[post.month] << post
13
+ @index[post.creation.month] << post
13
14
  end
14
15
 
15
16
  def archive_row(year_fmt, month_fmt)
@@ -25,16 +26,16 @@ module Aardi
25
26
  "./#{@archive_path}/#{self}/index.html"
26
27
  end
27
28
 
28
- def title = to_s
29
-
30
29
  private
31
30
 
31
+ def base_title = to_s
32
+
32
33
  def children
33
34
  months
34
35
  end
35
36
 
36
37
  def month_link(month)
37
- "- [#{Date::MONTHNAMES[month.key]}](#{Aardi.config[:site_url]}/#{@archive_path}/#{self}/#{month}/)"
38
+ "- [#{Date::MONTHNAMES[month.key]}](#{Config[:site_url]}/#{@archive_path}/#{self}/#{month}/)"
38
39
  end
39
40
 
40
41
  def months