dimples 4.3.2 → 5.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
- SHA1:
3
- metadata.gz: 6bdfb2eae683d3ef78e118e1096319d376db8434
4
- data.tar.gz: 402257f0a0610c45baef474482acbe5dc8bf7d94
2
+ SHA256:
3
+ metadata.gz: d7a2db6d510cc724696cd9255569144515c7ad9afd10b4a6da596f50f492d4f9
4
+ data.tar.gz: f55f43d79d5137c22ccea11e27c50be6de043ee794f3d3ae3ea212508da6bebb
5
5
  SHA512:
6
- metadata.gz: 81064093be0ee7d0c8dc535d99a49e2be396fc5d00132b83c6b869161d5fed9afddd7fdda1616feb496f1d86e235865230ab0fe7de07525e123a188b75954882
7
- data.tar.gz: fb65a564e482f7210beb4fa62c1e881847bbe1e0005ddaa1ce97661f4aae72a70a5ef77e0508f7b1e4ba4167c38aafdf999e9e0e03a7337effc565c476347333
6
+ metadata.gz: c86532b5690b9bc411d18357101799fe3e6e9c0a2c33ef9fabc2f3975b3fa9551cb45ae97768dcd96a6b0ec7388ceaba4bcdf0dc5a40b3a800ce10b3878c3085
7
+ data.tar.gz: 815be09d293fba723038a6fb0bbc6123434e03b34bdd7aa52438f276b234ea9e52cf661c2efa0c1120d72533c2149a81b2e5f8a87381ba1f666108b9166ed566
data/bin/dimples CHANGED
@@ -18,7 +18,7 @@ valid_commands = %w[build]
18
18
  options = Trollop.options do
19
19
  version "dimples v#{Dimples::VERSION}"
20
20
  banner <<-BANNER
21
- A very, very simple static site generator.
21
+ A simple static site generator.
22
22
 
23
23
  Usage:
24
24
  dimples <#{valid_commands.join('|')}> [options]
@@ -26,7 +26,6 @@ dimples <#{valid_commands.join('|')}> [options]
26
26
  Options:
27
27
  BANNER
28
28
  opt :config, 'Config file path', type: :string
29
- opt :lib, 'Library file path', default: 'lib'
30
29
  opt :verbose, 'Verbose mode', default: false
31
30
  end
32
31
 
@@ -37,7 +36,7 @@ unless valid_commands.include?(command)
37
36
  Trollop.die "Command must be '#{valid_commands.join('\', \'')}'"
38
37
  end
39
38
 
40
- lib_path = File.join(Dir.pwd, options[:lib])
39
+ plugins_path = File.join(Dir.pwd, 'plugins')
41
40
  config_path = options[:config] || File.join(Dir.pwd, 'config.json')
42
41
 
43
42
  if File.exist?(config_path)
@@ -53,43 +52,24 @@ else
53
52
  Trollop.die "Unable to find config file (#{config_path})"
54
53
  end
55
54
 
56
- config[:verbose_logging] = options[:verbose] if options[:verbose]
57
-
58
- if Dir.exist?(lib_path)
59
- Dir.glob(File.join(lib_path, '**', '*.rb')) do |path|
55
+ if Dir.exist?(plugins_path)
56
+ Dir.glob(File.join(plugins_path, '**', '*.rb')) do |path|
60
57
  require path
61
58
  end
62
59
  end
63
60
 
64
- site_klass_name = config.dig(:class_overrides, :site)
65
-
66
- site_klass = if site_klass_name
67
- Object.const_get(site_klass_name)
68
- else
69
- Dimples::Site
70
- end
71
-
72
- site = site_klass.new(config)
61
+ site = Dimples::Site.new(config)
73
62
 
74
63
  case command.to_sym
75
64
  when :build
76
- Dimples.logger.info("Building site at #{site.output_paths[:site]}...")
77
-
78
- result = Benchmark.measure do
79
- site.generate
80
- end
81
-
82
- if site.generated?
83
- generation_time = result.real.round(2)
65
+ puts 'Building site...'
84
66
 
85
- message = "\033[92mDone!\033[0m Site built in #{generation_time} second"
86
- message += 's' if generation_time != 1
87
- message += '.'
67
+ site.generate
88
68
 
89
- Dimples.logger.info(message)
69
+ if site.errors.empty?
70
+ puts 'Done! Your site has been built.'
90
71
  else
91
- site.errors.each do |error|
92
- Dimples.logger.error(error)
93
- end
72
+ puts 'Generation failed:'
73
+ site.errors.each { |error| puts error }
94
74
  end
95
75
  end
data/lib/dimples.rb CHANGED
@@ -2,30 +2,19 @@
2
2
 
3
3
  $LOAD_PATH.unshift(__dir__)
4
4
 
5
- require 'benchmark'
6
- require 'fileutils'
7
- require 'logger'
5
+ require 'hashie'
8
6
  require 'tilt'
9
7
  require 'yaml'
10
8
 
11
9
  require 'dimples/frontable'
12
- require 'dimples/renderable'
13
10
 
14
11
  require 'dimples/category'
15
12
  require 'dimples/configuration'
16
13
  require 'dimples/errors'
17
- require 'dimples/logger'
18
14
  require 'dimples/page'
19
- require 'dimples/pagination'
15
+ require 'dimples/pager'
16
+ require 'dimples/plugin'
20
17
  require 'dimples/post'
18
+ require 'dimples/renderer'
21
19
  require 'dimples/site'
22
20
  require 'dimples/template'
23
-
24
- # A static site generator.
25
- module Dimples
26
- class << self
27
- def logger
28
- @logger ||= Dimples::Logger.new(STDOUT)
29
- end
30
- end
31
- end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dimples
4
- # A class that models a single interview category.
5
4
  class Category
6
5
  attr_accessor :name
7
6
  attr_accessor :slug
@@ -10,12 +9,13 @@ module Dimples
10
9
  def initialize(site, slug)
11
10
  @site = site
12
11
  @slug = slug
13
- @name = @site.config[:category_names][slug.to_sym] || slug.capitalize
12
+ @name = @site.config.category_names[slug.to_sym] || slug.capitalize
13
+
14
14
  @posts = []
15
15
  end
16
16
 
17
17
  def inspect
18
- "#<#{self.class} @slug=#{@slug} @name=#{@name}>"
18
+ "#<#{self.class} @slug=#{slug} @name=#{name}>"
19
19
  end
20
20
  end
21
21
  end
@@ -1,74 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dimples
4
- # A class that models a site's configuration.
5
- class Configuration
6
- def initialize(config = {})
7
- @settings = Dimples::Configuration.default_settings
8
-
9
- config.each_key do |key|
10
- case @settings[key]
11
- when Hash
12
- @settings[key].merge!(config[key])
13
- else
14
- @settings[key] = config[key]
15
- end
16
- end
17
- end
18
-
19
- def [](key)
20
- @settings[key]
21
- end
22
-
23
- def self.default_settings
4
+ module Configuration
5
+ def self.defaults
24
6
  {
25
- source_path: Dir.pwd,
26
- destination_path: File.join(Dir.pwd, 'site'),
27
- verbose_logging: false,
28
- class_overrides: { site: nil, post: nil },
29
- rendering: {},
30
- category_names: {},
31
7
  paths: default_paths,
8
+ generation: default_generation,
32
9
  layouts: default_layouts,
33
10
  pagination: default_pagination,
34
- generation: default_generation,
35
- date_formats: default_date_formats
36
- }
37
- end
38
-
39
- def self.default_layouts
40
- {
41
- posts: 'posts',
42
- post: 'post',
43
- category: 'category',
44
- year_archives: 'year_archives',
45
- month_archives: 'month_archives',
46
- day_archives: 'day_archives'
11
+ date_formats: default_date_formats,
12
+ feed_formats: default_feed_formats,
13
+ category_names: {},
14
+ rendering: {},
47
15
  }
48
16
  end
49
17
 
50
18
  def self.default_paths
51
19
  {
20
+ output: 'site',
52
21
  archives: 'archives',
53
22
  posts: 'archives/%Y/%m/%d',
54
23
  categories: 'archives/categories'
55
24
  }
56
25
  end
57
26
 
58
- def self.default_pagination
59
- {
60
- per_page: 10
61
- }
62
- end
63
-
64
27
  def self.default_generation
65
28
  {
66
- categories: true,
29
+ archives: true,
67
30
  year_archives: true,
68
31
  month_archives: true,
69
32
  day_archives: true,
70
- feeds: true,
71
- category_feeds: true
33
+ categories: true,
34
+ main_feed: true,
35
+ category_feeds: true,
36
+ }
37
+ end
38
+
39
+ def self.default_layouts
40
+ {
41
+ post: 'post',
42
+ category: 'category',
43
+ archive: 'archive',
44
+ date_archive: 'archive'
72
45
  }
73
46
  end
74
47
 
@@ -79,5 +52,16 @@ module Dimples
79
52
  day: '%Y-%m-%d'
80
53
  }
81
54
  end
55
+
56
+ def self.default_feed_formats
57
+ ['atom']
58
+ end
59
+
60
+ def self.default_pagination
61
+ {
62
+ page_prefix: 'page',
63
+ per_page: 10
64
+ }
65
+ end
82
66
  end
83
67
  end
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dimples
4
- module Errors
5
- class PublishingError < StandardError
6
- end
4
+ class PublishingError < StandardError
5
+ end
7
6
 
8
- class RenderingError < StandardError
9
- end
7
+ class RenderingError < StandardError
8
+ end
10
9
 
11
- class GenerationError < StandardError
12
- end
10
+ class GenerationError < StandardError
13
11
  end
14
12
  end
@@ -1,23 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dimples
4
- # A mixin class that handles reading and parsing front matter from a file.
5
4
  module Frontable
6
- SKIPPED_METADATA_KEYS = %w[site path contents].freeze
5
+ def read_with_front_matter(path)
6
+ contents = File.read(path)
7
7
 
8
- def read_with_front_matter
9
- @contents = File.read(@path)
10
-
11
- matches = @contents.match(/^(-{3}\n.*?\n?)^(-{3}*$\n?)/m)
12
- return if matches.nil?
13
-
14
- YAML.safe_load(matches[1]).each_pair do |key, value|
15
- if !SKIPPED_METADATA_KEYS.include?(key) && respond_to?("#{key}=")
16
- send("#{key}=", value)
17
- end
8
+ if (matches = contents.match(/^(-{3}\n.*?\n?)^(-{3}*$\n?)/m))
9
+ metadata = Hashie.symbolize_keys(YAML.safe_load(matches[1]))
10
+ contents = matches.post_match.strip
11
+ else
12
+ metadata = {}
18
13
  end
19
14
 
20
- @contents = matches.post_match.strip
15
+ [contents, metadata]
21
16
  end
22
17
  end
23
18
  end
data/lib/dimples/page.rb CHANGED
@@ -1,67 +1,73 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dimples
4
- # A class that models a single site page.
5
4
  class Page
6
5
  include Frontable
7
- include Renderable
8
6
 
9
- attr_accessor :path
10
- attr_accessor :title
11
- attr_accessor :filename
12
- attr_accessor :extension
13
- attr_accessor :layout
14
7
  attr_accessor :contents
15
- attr_accessor :output_directory
8
+ attr_accessor :metadata
9
+ attr_accessor :path
16
10
 
17
11
  def initialize(site, path = nil)
18
12
  @site = site
19
- @extension = 'html'
20
13
  @path = path
21
14
 
22
15
  if @path
23
- @filename = File.basename(@path, File.extname(@path))
24
- @output_directory = File.dirname(@path).sub(
25
- @site.source_paths[:pages],
26
- @site.output_paths[:site]
27
- )
28
-
29
- read_with_front_matter
16
+ @contents, @metadata = read_with_front_matter(@path)
30
17
  else
31
- @filename = 'index'
32
18
  @contents = ''
33
- @output_directory = @site.output_paths[:site]
19
+ @metadata = {}
34
20
  end
35
21
  end
36
22
 
37
- def output_path
38
- File.join(@output_directory, output_filename)
23
+ def filename
24
+ @metadata[:filename] || 'index'
39
25
  end
40
26
 
41
- def output_filename
42
- "#{@filename}.#{@extension}"
27
+ def extension
28
+ @metadata[:extension] || 'html'
43
29
  end
44
30
 
45
- def url
46
- absolute_output_directory = File.absolute_path(@output_directory)
31
+ def render(context = {})
32
+ metadata = @metadata.dup
33
+ metadata.merge!(context[:page]) if context[:page]
47
34
 
48
- absolute_output_directory.sub(@site.output_paths[:site], '').tap do |url|
49
- url[0] = '/' unless url[0] == '/'
50
- url.concat('/') unless url[-1] == '/'
51
- url.concat(output_filename) if filename != 'index'
52
- end
35
+ context[:page] = Hashie::Mash.new(metadata)
36
+
37
+ renderer.render(context)
53
38
  end
54
39
 
55
- def write(context = {})
56
- FileUtils.mkdir_p(@output_directory) unless Dir.exist?(@output_directory)
40
+ def write(output_directory, context = {})
41
+ FileUtils.mkdir_p(output_directory) unless Dir.exist?(output_directory)
42
+ output_path = File.join(output_directory, "#{filename}.#{extension}")
43
+
57
44
  File.write(output_path, render(context))
58
45
  rescue SystemCallError => e
59
- error_message = "Failed to write #{path} (#{e.message})"
60
- raise Errors::PublishingError, error_message
46
+ raise PublishingError, "Failed to publish file at #{output_path} (#{e})"
61
47
  end
62
48
 
63
49
  def inspect
64
- "#<#{self.class} @output_path=#{output_path}>"
50
+ "#<#{self.class} @path=#{path}>"
51
+ end
52
+
53
+ private
54
+
55
+ def renderer
56
+ @renderer ||= Renderer.new(@site, self)
57
+ end
58
+
59
+ def method_missing(method_name, *args, &block)
60
+ if @metadata.key?(method_name)
61
+ @metadata[method_name]
62
+ elsif (matches = method_name.match(/([a-z_]+)=/))
63
+ @metadata[matches[1].to_sym] = args[0]
64
+ else
65
+ super
66
+ end
67
+ end
68
+
69
+ def respond_to_missing?(method_name, include_private = false)
70
+ @metadata.key?(method_name) || method_name.match?(/([a-z_]+)=/) || super
65
71
  end
66
72
  end
67
73
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dimples
4
+ class Pager
5
+ include Enumerable
6
+
7
+ attr_reader :current_page
8
+ attr_reader :previous_page
9
+ attr_reader :next_page
10
+ attr_reader :page_count
11
+ attr_reader :item_count
12
+
13
+ def initialize(url, posts, options = {})
14
+ @url = url
15
+ @posts = posts
16
+ @per_page = options[:per_page] || 10
17
+ @page_prefix = options[:page_prefix] || 'page'
18
+ @page_count = (posts.length.to_f / @per_page.to_i).ceil
19
+
20
+ step_to(1)
21
+ end
22
+
23
+ def each(&block)
24
+ (1..@page_count).each do |index|
25
+ block.yield step_to(index)
26
+ end
27
+ end
28
+
29
+ def step_to(page)
30
+ @current_page = (1..@page_count).cover?(page) ? page : 1
31
+ @previous_page = (@current_page - 1).positive? ? @current_page - 1 : nil
32
+ @next_page = @current_page + 1 <= @page_count ? @current_page + 1 : nil
33
+
34
+ @current_page
35
+ end
36
+
37
+ def posts_at(page)
38
+ @posts.slice((page - 1) * @per_page, @per_page)
39
+ end
40
+
41
+ def current_page_url
42
+ @current_page != 1 ? "#{@url}#{@page_prefix}#{@current_page}" : @url
43
+ end
44
+
45
+ def first_page_url
46
+ @url
47
+ end
48
+
49
+ def last_page_url
50
+ @page_count != 1 ? "#{@url}#{@page_prefix}#{@page_count}" : @url
51
+ end
52
+
53
+ def previous_page_url
54
+ return unless @previous_page
55
+ @previous_page != 1 ? "#{@url}#{@page_prefix}#{@previous_page}" : @url
56
+ end
57
+
58
+ def next_page_url
59
+ "#{@url}#{@page_prefix}#{@next_page}" if @next_page
60
+ end
61
+
62
+ def to_context
63
+ Hashie::Mash.new(
64
+ posts: posts_at(current_page),
65
+ current_page: @current_page,
66
+ page_count: @page_count,
67
+ post_count: @posts.count,
68
+ previous_page: @previous_page,
69
+ next_page: @next_page,
70
+ urls: {
71
+ current_page: current_page_url,
72
+ first_page: first_page_url,
73
+ last_page: last_page_url,
74
+ previous_page: previous_page_url,
75
+ next_page: next_page_url
76
+ }
77
+ )
78
+ end
79
+ end
80
+ end