Skip to content
This repository has been archived by the owner on Nov 10, 2022. It is now read-only.

Add option to set page.date automatically with the file creation date #1

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
_site/
Gemfile.lock
spec/fixtures/_posts/1992-09-11-last-modified-at.md
spec/fixtures/_posts/date-no.md
spec/fixtures/.jekyll-metadata
spec/fixtures/.jekyll-cache
spec/dev/out.txt
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,18 @@ To format such a time, you'll need to rely on Liquid's `date` filter:
```

(It's generally [more performant to use the `page.last_modified_at` version](https://github.com/gjtorikian/jekyll-last-modified-at/issues/24#issuecomment-55431108) of this plugin.)

## `page.date`

Additionally, this plugin automatically sets a default `date` value on every page based on when the file was **first** commited in git. To disable this, set `set-page-date` to `false` in your config yaml:

```yml
plugins:
- jekyll-last-modified-at

last-modified-at:
set-page-date: false
```

If a post's date is already set via [the filename](https://jekyllrb.com/docs/posts/#creating-posts) or a page's date is set in its [frontmatter](https://jekyllrb.com/docs/variables/#page-variables), those values will override the value provided by this plugin. If a git date isn't available, `ctime` is used.

68 changes: 59 additions & 9 deletions lib/jekyll-last-modified-at/determinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,63 @@ module Jekyll
module LastModifiedAt
class Determinator
@repo_cache = {}
@path_cache = {}
@last_mod_cache = {}
@first_mod_cache = {}
class << self
# attr_accessor so we can flush externally
attr_accessor :repo_cache
attr_accessor :path_cache
attr_accessor :last_mod_cache
attr_accessor :first_mod_cache
end

attr_reader :site_source, :page_path, :use_git_cache
attr_accessor :format

def initialize(site_source, page_path, format = nil, use_git_cache = true) # rubocop:disable Style/OptionalBooleanParameter
def initialize(site_source, page_path, format = nil, use_git_cache = true, first_time = false) # rubocop:disable Style/OptionalBooleanParameter
@site_source = site_source
@page_path = page_path
@format = format || '%d-%b-%y'
@use_git_cache = use_git_cache
@first_time = first_time
end

def git
return self.class.repo_cache[site_source] unless self.class.repo_cache[site_source].nil?

self.class.repo_cache[site_source] = Git.new(site_source)
self.class.repo_cache[site_source]
end

def formatted_last_modified_date
last_modified_at_time.strftime(@format)
end

def formatted_first_modified_date
first_modified_at_time.strftime(@format)
end

def first_modified_at_time
return self.class.first_mod_cache[page_path] unless self.class.first_mod_cache[page_path].nil?

raise Errno::ENOENT, "#{absolute_path_to_article} does not exist!" unless File.exist? absolute_path_to_article

self.class.first_mod_cache[page_path] = Time.at(first_modified_at_unix.to_i)
end

def first_modified_at_unix
if git.git_repo?
first_commit_date = git.first_commit_date(relative_path_from_git_dir, use_git_cache)
first_commit_date.nil? || first_commit_date.empty? ? ctime(absolute_path_to_article) : first_commit_date
else
ctime(absolute_path_to_article)
end
end

def last_modified_at_time
return self.class.path_cache[page_path] unless self.class.path_cache[page_path].nil?
return self.class.last_mod_cache[page_path] unless self.class.last_mod_cache[page_path].nil?

raise Errno::ENOENT, "#{absolute_path_to_article} does not exist!" unless File.exist? absolute_path_to_article

self.class.path_cache[page_path] = Time.at(last_modified_at_unix.to_i)
self.class.path_cache[page_path]
self.class.last_mod_cache[page_path] = Time.at(last_modified_at_unix.to_i)
end

def last_modified_at_unix
Expand All @@ -51,12 +73,36 @@ def last_modified_at_unix
end
end

def to_i
if @first_time
@to_i ||= first_modified_at_unix.to_i
else
@to_i ||= last_modified_at_unix.to_i
end
end

def to_s
@to_s ||= formatted_last_modified_date
if @first_time
@to_s ||= formatted_first_modified_date
else
@to_s ||= formatted_last_modified_date
end
end

def to_liquid
@to_liquid ||= last_modified_at_time
if @first_time
@to_liquid ||= first_modified_at_time
else
@to_liquid ||= last_modified_at_time
end
end

def to_time
to_liquid
end

def strftime(*args)
return to_liquid().strftime(*args)
end

private
Expand All @@ -77,6 +123,10 @@ def relative_path_from_git_dir
def mtime(file)
File.mtime(file).to_i.to_s
end

def ctime(file)
File.ctime(file).to_i.to_s
end
end
end
end
43 changes: 30 additions & 13 deletions lib/jekyll-last-modified-at/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def initialize(site_source)
@site_source = site_source
@is_git_repo = nil
@lcd_cache = {}
@lce_cache = {}
end

def top_level_directory
Expand Down Expand Up @@ -37,7 +38,7 @@ def git_repo?

def last_commit_date(path, use_git_cache = false) # rubocop:disable Style/OptionalBooleanParameter
if use_git_cache
build_lcd_cache if @lcd_cache.empty?
build_cache if @lcd_cache.empty?
@lcd_cache[path]
else
Executor.sh(
Expand All @@ -54,16 +55,35 @@ def last_commit_date(path, use_git_cache = false) # rubocop:disable Style/Option
end
end

def first_commit_date(path, use_git_cache = false) # rubocop:disable Style/OptionalBooleanParameter
if use_git_cache
build_cache if @lce_cache.empty?
@lce_cache[path]
else
Executor.sh(
'git',
'--git-dir',
top_level_directory,
'log',
'--follow',
'--diff-filter=A',
'--format="%ct"',
'--',
path
)&.split("\n")&[-1]&[/\d+/]
end
end

private

# generates hash of `path => unix time stamp (string)`
def build_lcd_cache
def build_cache
# example output:
#
# %jekyll-last-modified-at:1621042992
# %these-files-modified-at:1621042992
#
# Dockerfile.production
# %jekyll-last-modified-at:1621041929
# %these-files-modified-at:1621041929
#
# assets/css/style.52513a5600efd4015668ccb9b702256e.css
# assets/css/style.52513a5600efd4015668ccb9b702256e.css.gz
Expand All @@ -74,24 +94,21 @@ def build_lcd_cache
'log',
'--name-only',
'--date=unix',
'--pretty=%%jekyll-last-modified-at:%ct'
'--pretty=%%these-files-modified-at:%ct'
)

lcd = nil
timestamp = nil
lines.split("\n").each do |line|
next if line.empty?

if line.start_with?('%jekyll-last-modified-at:')
if line.start_with?('%these-files-modified-at:')
# new record
lcd = line.split(':')[1]
timestamp = line.split(':')[1]
next
end

# we already have it
next if @lcd_cache[line]

# we don't have it
@lcd_cache[line] = lcd
@lcd_cache[line] = timestamp unless @lcd_cache.key?(line)
@lce_cache[line] = timestamp
end
end
end
Expand Down
17 changes: 7 additions & 10 deletions lib/jekyll-last-modified-at/hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,18 @@ module Hook
def self.add_determinator_proc
proc { |item|
format = item.site.config.dig('last-modified-at', 'date-format')
use_git_cache = item.site.config.dig('last-modified-at', 'use-git-cache')
use_git_cache = item.site.config.dig('last-modified-at', 'use-git-cache') != false
item.data['last_modified_at'] = Determinator.new(item.site.source, item.relative_path,
format, use_git_cache)
if item.site.config.dig('last-modified-at', 'set-page-date') != false
# The "date" field will be converted to a string first by Jekyll and it must be
# in the format given below: https://jekyllrb.com/docs/variables/#page-variables
item.data['date'] = Determinator.new(item.site.source, item.relative_path,
'%Y-%m-%d %H:%M:%S %z', use_git_cache, true)
end
}
end

Jekyll::Hooks.register :site, :after_reset do |site|
use_git_cache = site.config.dig('last-modified-at', 'use-git-cache')
if use_git_cache
# flush the caches so we can detect commits while server is running
Determinator.repo_cache = {}
Determinator.path_cache = {}
end
end

Jekyll::Hooks.register :posts, :post_init, &Hook.add_determinator_proc
Jekyll::Hooks.register :pages, :post_init, &Hook.add_determinator_proc
Jekyll::Hooks.register :documents, :post_init, &Hook.add_determinator_proc
Expand Down
10 changes: 10 additions & 0 deletions spec/fixtures/_layouts/date_with_format.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<title>date-with-format</title>
</head>
<body>
<span class="last-modified-at">Article created on {% page.date | date: "%Y:%B:%A:%d" %}</span>

{{ content }}
</body>
</html>
6 changes: 6 additions & 0 deletions spec/fixtures/_posts/no-date.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Testing Last Modified At
layout: date_with_format
---

Yay.
31 changes: 31 additions & 0 deletions spec/jekyll-last-modified-at/date_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'Date Tag' do
context 'A committed post file' do
def setup(file, layout)
@post = setup_post(file)
do_render(@post, layout)
end

it 'has date' do
setup('no-date.md', 'date_with_format.html')
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently struggling with:

  1) Date Tag A committed post file has date
     Failure/Error: post.output = Renderer.new(@site, post, @site.site_payload).run
     
     Liquid::SyntaxError:
       Liquid syntax error (line 6): Unknown tag 'page'

Removing page. from spec/fixtures/_layouts/date_with_format.html (so it's just date) doesn't change anything. I'm probably missing something obvious but it's tired and I'm late.

expect(@post.output).to match(/Article created on 09-Nov-22/)
end
end

context 'An uncommitted post file' do
before(:all) do
cheater_file = 'no-date.md'
uncommitted_file = 'date_no.md'
duplicate_post(cheater_file, uncommitted_file)
@post = setup_post(uncommitted_file)
do_render(@post, 'date_with_format.html')
end

it 'has date' do
expect(@post.output).to match(Regexp.new("Article created on #{Time.new.utc.strftime('%d-%b-%y')}"))
end
end
end