sawsge 0.1.3 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8cb16b8c9c5893d01c2d1fc0ba82457e24a9f6fcb670781f9990aecac4773ba
4
- data.tar.gz: 9388f1a50ff785504b8e5b9463b45418f25f9b43de33f93838d06742b8ab6e02
3
+ metadata.gz: b2a7067cfade9bf1831c7bf6f46d09bd23912b8f02ca9739ab3b281383a1348b
4
+ data.tar.gz: 04cb0c0e02a735460b7f3b0688abeac45d771ec702b6a8227d610b134458345c
5
5
  SHA512:
6
- metadata.gz: cb1c6014f224e896b001420fbf3c96a30b16571d1dbadcc05f6d5f623b7bf550692b3dbccd721453ca520a8b74df8ada8e9cccfc9e41c3d31959aa62428c1231
7
- data.tar.gz: 67e941e2c2ea82468bf51b30232fcfb32e9103b4d762336652d29de3211997217d265cafe349b0f74c7db534ef27713974edf26d8d4baeeec25463d4b0b61240
6
+ metadata.gz: b458f8adb18c62f8e6b101ecc572ae41bce854e8970aa0564fbec70e7b278bd133795b69ff1255fa13bc7d027fcc4c223cab06bff8fa92c908714f109b6006bd
7
+ data.tar.gz: 4e1da958f57c9b4feacbe0d655e4db6093ef977efb8ed6a544791146582177fe44c96339e54c1710ede9cd317a877228f39cdab7348287c9c236d42f84da56d6
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/bin/sawsge CHANGED
@@ -3,4 +3,4 @@
3
3
 
4
4
  require 'sawsge'
5
5
 
6
- Sawsge.sawsge
6
+ Sawsge.cli
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Sawsge
4
+ def blog
5
+ home_path = 'index.md'
6
+
7
+ # Does not work if you have parent directories for your
8
+ # posts dir, e.g. you set posts_dirname in config.toml to
9
+ # foo/bar/baz/etc...
10
+ post_paths = @resource_paths.select do |path|
11
+ top_parent_dir(path) == @config.posts_dirname && File.extname(path) == '.md'
12
+ end
13
+
14
+ @resource_paths -= post_paths
15
+ @resource_paths.delete home_path
16
+
17
+ post_objects = post_paths.map { |path| Post.new(path, @config) }
18
+
19
+ post_objects.sort_by! { |x| x.date }
20
+ # Posts are now in reverse chronological order
21
+
22
+ i_last_nil_date = 0
23
+ while post_objects[i_last_nil_date].date.empty?
24
+ i_last_nil_date += 1
25
+ end
26
+
27
+ post_objects.rotate!(i_last_nil_date).reverse!
28
+ # Posts are now in chronological order with dateless
29
+ # posts being first
30
+
31
+ home_object = Home.new(home_path, post_objects, @config)
32
+ @all_objects = post_objects + [home_object]
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tomlrb'
4
+
5
+ class Sawsge
6
+ class Config
7
+ attr_reader :external_links_target_blank,
8
+ :posts_dirname, :footer_filename, :reserved_filenames,
9
+ :src_dir, :out_dirname, :mode, :header_filename,
10
+ :header_path, :footer_path
11
+
12
+ def initialize(src_dir)
13
+ config_path = File.join(src_dir, CONFIG_FILENAME)
14
+ config = Tomlrb.load_file(config_path, symbolize_keys: true)
15
+
16
+ @src_dir = File.expand_path src_dir
17
+
18
+ @out_dirname = config.dig(:general, :out_dirname)
19
+ @mode = config[:general][:mode]
20
+
21
+ @header_filename = config.dig(:general, :header_filename)
22
+ @footer_filename = config.dig(:general, :footer_filename)
23
+
24
+ @header_path = File.expand_path(File.join(@src_dir, @header_filename))
25
+ @footer_path = File.expand_path(File.join(@src_dir, @footer_filename))
26
+
27
+ ignored_files = config[:general][:ignore] || []
28
+ @reserved_filenames = ignored_files + [CONFIG_FILENAME, @header_filename, @footer_filename]
29
+
30
+ @external_links_target_blank = config[:general][:external_links_target_blank] || true
31
+
32
+ @posts_dirname = config[:blog][:posts_dirname]
33
+ end
34
+ end
35
+ end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Sawsge
3
+ class Sawsge
4
4
  # The homepage
5
5
  class Home < Page
6
- def initialize(path, posts)
7
- super(path)
8
- posts.each do |post|
6
+ def initialize(path, posts, config)
7
+ super(path, config)
8
+
9
+ posts.each_with_index do |post, _i|
9
10
  # Adds collapseable summary of each post on the front
10
11
  # page
11
12
  summary_fragment = Nokogiri::HTML5.fragment <<~HTML
@@ -19,6 +20,7 @@ module Sawsge
19
20
  HTML
20
21
  @document.at_css('footer').add_previous_sibling summary_fragment
21
22
  end
23
+
22
24
  end
23
25
  end
24
26
  end
@@ -1,29 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Sawsge
3
+ class Sawsge
4
4
  # An HTML page
5
5
  class Page < Resource
6
6
  attr_reader :title
7
7
 
8
- def initialize(path)
8
+ def initialize(path, config)
9
9
  super(path)
10
- html_body_fragment = PandocRuby.convert(File.new(@path, 'r').read, from: :markdown, to: :html)
11
- @document = Nokogiri::HTML5(HEADER + FOOTER)
12
- @body = Nokogiri::HTML5.fragment(html_body_fragment)
10
+
11
+ markdown = File.read(@path)
12
+ options = {
13
+ :from => :markdown,
14
+ :to => :html
15
+ }
16
+ html_body_fragment = PandocRuby.convert(markdown, options)
17
+
18
+ header = File.read config.header_path
19
+ footer = File.read config.footer_path
20
+
21
+ # @document = Nokogiri::HTML5(header + footer)
22
+ # @body = Nokogiri::HTML5.fragment(header + html_body_fragment + footer)
23
+ @document = Nokogiri::HTML5(header + html_body_fragment + footer)
13
24
 
14
25
  # Place body fragment after header (and before footer)
15
- header = @document.at_css('header')
16
- @body = header.add_next_sibling(@body)
26
+ # header_location = @document.at_css('header')
27
+ # @body = header.add_next_sibling(@body)
17
28
 
18
29
  # Parse the body fragment instead of the whole document,
19
30
  # as the header may have another h1 within
20
31
  @title = begin
21
- h1 = @body.at_css('h1')
32
+ h1 = @document.at_css('h1')
22
33
  h1 ? h1.content : ''
23
34
  end
24
35
  @document.at_css('title').content = @title
25
36
 
26
- if EXTERNAL_LINKS_TARGET_BLANK
37
+ if config.external_links_target_blank
27
38
  # For any `a` tag where the href attribute has no
28
39
  # hostname (external link) and no existing `target`
29
40
  # attribute, add an attribute `target` with value
@@ -44,10 +55,10 @@ module Sawsge
44
55
  end
45
56
  end
46
57
 
47
- def build
58
+ def build(out_dirname)
48
59
  serialized_html = @document.serialize
49
- out_path = File.join(OUT_DIRNAME, @path.sub('index.md', 'index.html'))
50
- out_dir = File.join(OUT_DIRNAME, File.dirname(@path))
60
+ out_path = File.join(out_dirname, @path.sub('index.md', 'index.html'))
61
+ out_dir = File.join(out_dirname, File.dirname(@path))
51
62
 
52
63
  FileUtils.mkpath out_dir
53
64
  File.new(out_path, 'w').syswrite(serialized_html)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Sawsge
4
+ # A blogpost style HTML page
5
+ class Post < Page
6
+ attr_reader :date, :summary
7
+
8
+ def initialize(path, config)
9
+ super(path, config)
10
+
11
+ # Get the summary and date of the post
12
+ @summary = @document.css('summary').first.content
13
+ # If a date is specified within the <time> tag on the
14
+ # page, use that, otherwise use an empty string
15
+ @date = begin
16
+ date = @document.css('time').first
17
+ date ? date.content : ''
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Sawsge
4
- def self.project
3
+ class Sawsge
4
+ def project
5
5
  page_paths = @resource_paths.select { |path| File.extname(path) == '.md' }
6
6
 
7
- @resource_paths.subtract page_paths
7
+ @resource_paths -= page_paths
8
8
 
9
- page_objects = page_paths.map { |path| Page.new(path) }
9
+ page_objects = page_paths.map { |path| Page.new(path, @config) }
10
10
 
11
11
  @all_objects.merge page_objects
12
12
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Sawsge
3
+ class Sawsge
4
4
  # Any generic file in the website directory
5
5
  class Resource
6
6
  attr_reader :path
@@ -10,9 +10,9 @@ module Sawsge
10
10
  @path = path
11
11
  end
12
12
 
13
- def build
14
- FileUtils.mkpath File.join(OUT_DIRNAME, File.dirname(@path))
15
- FileUtils.cp @path, File.join(OUT_DIRNAME, @path)
13
+ def build(out_dirname)
14
+ FileUtils.mkpath File.join(out_dirname, File.dirname(@path))
15
+ FileUtils.cp @path, File.join(out_dirname, @path)
16
16
  end
17
17
  end
18
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Sawsge
4
- VERSION = '0.1.3'
3
+ class Sawsge
4
+ VERSION = '1.0.0'
5
5
  end
data/lib/sawsge.rb CHANGED
@@ -3,77 +3,66 @@
3
3
  require 'fileutils'
4
4
  require 'nokogiri'
5
5
  require 'pandoc-ruby'
6
+ require 'parallel'
6
7
  require 'pathname'
7
8
  require 'set'
8
- require 'toml'
9
+ require 'tomlrb'
9
10
  require 'uri'
10
11
 
11
- require_relative 'resource'
12
- require_relative 'page'
13
- require_relative 'post'
14
- require_relative 'home'
15
- require_relative 'blog'
16
- require_relative 'project'
12
+ require 'sawsge/resource'
13
+ require 'sawsge/page'
14
+ require 'sawsge/post'
15
+ require 'sawsge/home'
16
+ require 'sawsge/blog'
17
+ require 'sawsge/project'
18
+ require 'sawsge/config'
19
+
20
+ # Returns the first directory in a path, eg.
21
+ # `foo/bar/bin.txt` becomes `foo`
22
+ def top_parent_dir(path)
23
+ Pathname.new(path).each_filename.to_a[0]
24
+ end
17
25
 
18
- module Sawsge
26
+ class Sawsge
19
27
  HELP_STRING = 'Usage: sawsge [DIRECTORY]'
20
28
 
21
- SRC_DIR = ARGV[0] || Dir.pwd
22
-
23
29
  CONFIG_FILENAME = 'config.toml'
24
- CONFIG_STRING = File.read(File.join(SRC_DIR, CONFIG_FILENAME))
25
- CONFIG = TOML::Parser.new(CONFIG_STRING).parsed
26
-
27
- OUT_DIRNAME = CONFIG['general']['out_dirname']
28
-
29
- # TODO: Put these in the config
30
- POSTS_DIRNAME = CONFIG['blog']['posts_dirname']
31
-
32
- HEADER_FILENAME = CONFIG['general']['header_filename']
33
- FOOTER_FILENAME = CONFIG['general']['footer_filename']
34
30
 
35
- HEADER = HEADER_FILENAME.empty? ? '' : File.read(File.join(SRC_DIR, HEADER_FILENAME))
36
- FOOTER = FOOTER_FILENAME.empty? ? '' : File.read(File.join(SRC_DIR, FOOTER_FILENAME))
37
-
38
- EXTERNAL_LINKS_TARGET_BLANK = CONFIG['general']['external_links_target_blank']
39
-
40
- IGNORE = CONFIG['general']['ignore']
41
-
42
- # Resources that will not be put into the out folder
43
- RESERVED_FILENAMES = [CONFIG_FILENAME, HEADER_FILENAME, FOOTER_FILENAME] + IGNORE
44
-
45
- MODE = CONFIG['general']['mode']
31
+ def initialize(src_dir)
32
+ @config = Config.new(src_dir)
33
+ end
46
34
 
47
- # Returns the first directory in a path, eg.
48
- # `foo/bar/bin.txt` becomes `foo`
49
- def self.top_parent_dir(path)
50
- Pathname.new(path).each_filename.to_a[0]
35
+ def self.cli
36
+ src_dir = ARGV[0] || Dir.pwd
37
+ new(src_dir).build
51
38
  end
52
39
 
53
- def self.sawsge
40
+ def build
54
41
  # Gross, but easy
55
- Dir.chdir SRC_DIR
42
+ Dir.chdir @config.src_dir
56
43
 
57
44
  # Find all files recursively
58
- @resource_paths = Set.new(Dir.glob('**/*').select do |path|
45
+ @resource_paths = Dir.glob('**/*').select do |path|
59
46
  File.file?(path) &&
60
- top_parent_dir(path) != OUT_DIRNAME &&
61
- !RESERVED_FILENAMES.include?(path)
62
- end)
47
+ top_parent_dir(path) != @config.out_dirname &&
48
+ # Exclude explicitly ignored files
49
+ !@config.reserved_filenames.include?(path)
50
+ end
63
51
 
64
52
  @resource_objects = Set.new
65
53
  @all_objects = Set.new
66
54
 
67
- send MODE
55
+ # Execute blog or project specific code.
56
+ send @config.mode
68
57
 
69
58
  resources = @resource_paths.map { |path| Resource.new(path) }
70
- @all_objects.merge resources
59
+ @all_objects += resources
71
60
 
72
61
  # Delete any old builds
73
- FileUtils.remove_dir OUT_DIRNAME if Pathname.new(OUT_DIRNAME).exist?
74
- FileUtils.mkpath OUT_DIRNAME
62
+ FileUtils.remove_dir @config.out_dirname if Pathname.new(@config.out_dirname).exist?
63
+ FileUtils.mkpath @config.out_dirname
75
64
 
76
65
  # Write each file
77
- @all_objects.each(&:build)
66
+ Parallel.each(@all_objects) { |x| x.build @config.out_dirname }
78
67
  end
79
68
  end
data/sawsge.gemspec CHANGED
@@ -10,24 +10,27 @@ Gem::Specification.new do |s|
10
10
  s.description = 'Sawsge is an opinionated static site generator with TOML-configurable modes for blogs and projects. It takes Markdown files as source and uses Pandoc behind the scences to generate HTML files.'
11
11
  s.authors = ['Sawyer Shepherd']
12
12
  s.email = 'contact@sawyershepherd.org'
13
- s.files = [
14
- 'LICENSE',
15
- 'README.md',
16
- 'bin/sawsge',
17
- 'sawsge.gemspec',
18
- 'lib/sawsge/version.rb',
19
- 'lib/sawsge.rb',
20
- 'lib/blog.rb',
21
- 'lib/home.rb',
22
- 'lib/page.rb',
23
- 'lib/post.rb',
24
- 'lib/project.rb',
25
- 'lib/resource.rb'
26
-
13
+ s.files = %w[
14
+ Gemfile
15
+ LICENSE
16
+ README.md
17
+ bin/sawsge
18
+ lib/sawsge.rb
19
+ lib/sawsge/blog.rb
20
+ lib/sawsge/config.rb
21
+ lib/sawsge/home.rb
22
+ lib/sawsge/page.rb
23
+ lib/sawsge/post.rb
24
+ lib/sawsge/project.rb
25
+ lib/sawsge/resource.rb
26
+ lib/sawsge/version.rb
27
+ sawsge.gemspec
27
28
  ]
28
29
  s.homepage = 'https://github.com/sawshep/sawsge'
29
30
  s.license = 'GPL-3.0'
30
31
  s.required_ruby_version = '>= 3.0'
31
32
  s.add_runtime_dependency 'pandoc-ruby', '~> 2.1'
32
- s.add_runtime_dependency 'toml', '~> 0.3'
33
+ s.add_runtime_dependency 'tomlrb', '~> 2.0', '>= 2.0.1'
34
+ s.add_runtime_dependency 'nokogiri', '~> 1.12', '>= 1.12.4'
35
+ s.add_runtime_dependency 'parallel', '~> 1.22', '>= 1.22.1'
33
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sawsge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sawyer Shepherd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-19 00:00:00.000000000 Z
11
+ date: 2022-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pandoc-ruby
@@ -25,19 +25,65 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.1'
27
27
  - !ruby/object:Gem::Dependency
28
- name: toml
28
+ name: tomlrb
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.3'
33
+ version: '2.0'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 2.0.1
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '2.0'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.0.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: nokogiri
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.12'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 1.12.4
34
57
  type: :runtime
35
58
  prerelease: false
36
59
  version_requirements: !ruby/object:Gem::Requirement
37
60
  requirements:
38
61
  - - "~>"
39
62
  - !ruby/object:Gem::Version
40
- version: '0.3'
63
+ version: '1.12'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 1.12.4
67
+ - !ruby/object:Gem::Dependency
68
+ name: parallel
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.22'
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 1.22.1
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.22'
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 1.22.1
41
87
  description: Sawsge is an opinionated static site generator with TOML-configurable
42
88
  modes for blogs and projects. It takes Markdown files as source and uses Pandoc
43
89
  behind the scences to generate HTML files.
@@ -47,16 +93,18 @@ executables:
47
93
  extensions: []
48
94
  extra_rdoc_files: []
49
95
  files:
96
+ - Gemfile
50
97
  - LICENSE
51
98
  - README.md
52
99
  - bin/sawsge
53
- - lib/blog.rb
54
- - lib/home.rb
55
- - lib/page.rb
56
- - lib/post.rb
57
- - lib/project.rb
58
- - lib/resource.rb
59
100
  - lib/sawsge.rb
101
+ - lib/sawsge/blog.rb
102
+ - lib/sawsge/config.rb
103
+ - lib/sawsge/home.rb
104
+ - lib/sawsge/page.rb
105
+ - lib/sawsge/post.rb
106
+ - lib/sawsge/project.rb
107
+ - lib/sawsge/resource.rb
60
108
  - lib/sawsge/version.rb
61
109
  - sawsge.gemspec
62
110
  homepage: https://github.com/sawshep/sawsge
data/lib/blog.rb DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sawsge
4
- def self.blog
5
- home_path = 'index.md'
6
-
7
- # Does not work if you have parent directories for your
8
- # posts dir, e.g. you set posts_dirname in config.toml to
9
- # foo/bar/baz/etc...
10
- post_paths = resource_paths.select do |path|
11
- top_parent_dir(path) == POSTS_DIRNAME && File.extname(path) == '.md'
12
- end
13
- # So posts are added to Home in chronological order
14
- post_paths.reverse!
15
-
16
- @resource_paths.subtract post_paths
17
- @resource_paths.delete home_path
18
-
19
- post_objects = post_paths.map { |path| Post.new(path) }
20
- home_object = Home.new(home_path, post_objects)
21
- @all_objects = post_objects + [home_object]
22
- end
23
- end
data/lib/post.rb DELETED
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sawsge
4
- # A blogpost style HTML page
5
- class Post < Page
6
- attr_reader :date, :summary
7
-
8
- def initialize(path)
9
- super(path)
10
-
11
- # There's got to be a more idiomatic way to do this! The
12
- # current implementation is disguisting.
13
- # Also doesn't work if POSTS_DIRNAME is more than 2
14
- # directories
15
- parts = Pathname.new(@path).each_filename.to_a[1..]
16
- parts.delete(POSTS_DIRNAME)
17
- @date = "#{parts[0]}-#{parts[1]}-#{parts[2]}"
18
- @body.css('h1').first.add_next_sibling "<date>#{@date}</date>"
19
-
20
- # Look what's in <summary></summary>
21
- @summary = @document.css('summary').first.content
22
- end
23
- end
24
- end