sawsge 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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