pax_global_header00006660000000000000000000000064136525350030014514gustar00rootroot0000000000000052 comment=173bcfda0375fab2f49ea9f5dfaa36d8aa957056 jekyll-last-modified-at-1.3.0/000077500000000000000000000000001365253500300161305ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/.github/000077500000000000000000000000001365253500300174705ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/.github/FUNDING.yml000066400000000000000000000010521365253500300213030ustar00rootroot00000000000000# These are supported funding model platforms github: gjtorikian patreon: gjtorikian open_collective: garen-torikian #ko_fi: # Replace with a single Ko-fi username #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry #liberapay: # Replace with a single Liberapay username issuehunt: gjtorikian #otechie: # Replace with a single Otechie username #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] jekyll-last-modified-at-1.3.0/.gitignore000066400000000000000000000002531365253500300201200ustar00rootroot00000000000000*.gem _site/ Gemfile.lock spec/fixtures/_posts/1992-09-11-last-modified-at.md spec/fixtures/.jekyll-metadata spec/fixtures/.jekyll-cache spec/dev/out.txt spec/dev/err.txt jekyll-last-modified-at-1.3.0/.rubocop.yml000066400000000000000000000003121365253500300203760ustar00rootroot00000000000000inherit_gem: rubocop-standard: - config/default.yml Style/StringLiterals: Enabled: true EnforcedStyle: single_quotes Naming/FileName: Enabled: false Style/Documentation: Enabled: false jekyll-last-modified-at-1.3.0/.travis.yml000066400000000000000000000002341365253500300202400ustar00rootroot00000000000000language: ruby rvm: - 2.3 - 2.4 - 2.5 - 2.6 sudo: false cache: bundler matrix: include: - script: bundle exec rake rubocop rvm: 2.6.0 jekyll-last-modified-at-1.3.0/Gemfile000066400000000000000000000002041365253500300174170ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec gem 'jekyll', ENV['JEKYLL_VERSION'] if ENV['JEKYLL_VERSION'] jekyll-last-modified-at-1.3.0/LICENSE000066400000000000000000000020741365253500300171400ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Garen J. Torikian Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jekyll-last-modified-at-1.3.0/README.md000066400000000000000000000027221365253500300174120ustar00rootroot00000000000000# Last Modified At Plugin A liquid tag for Jekyll to indicate the last time a file was modified. This plugin determines a page's last modified date by checking the last Git commit date of source files. In the event Git is not available, the file's `mtime` is used. ## Setting up Open your Gemfile in your Jekyll root folder and add the following: ``` ruby group :jekyll_plugins do gem "jekyll-last-modified-at" end ``` Add the following to your site's `_config.yml` file ```yml plugins: - jekyll-last-modified-at # Optional. The default date format, used if none is specified in the tag. last-modified-at: date-format: '%d-%b-%y' ``` ## Usage There are a few ways to use this gem. You can place the following tag somewhere within your layout: ``` liquid {% last_modified_at %} ``` By default, this creates a time format matching `"%d-%b-%y"` (like "04-Jan-14"). You can also choose to pass along your own time format. For example: ```liquid {% last_modified_at %Y:%B:%A:%d:%S:%R %} ``` That produces "2014:January:Saturday:04." You can also call the method directly on a Jekyll "object," like so: ``` liquid {{ page.last_modified_at }} ``` To format such a time, you'll need to rely on Liquid's `date` filter: ``` liquid {{ page.last_modified_at | date: '%Y:%B:%A:%d:%S:%R' }} ``` (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.) jekyll-last-modified-at-1.3.0/Rakefile000077500000000000000000000003471365253500300176040ustar00rootroot00000000000000# frozen_string_literal: true require 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) task default: :spec require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) jekyll-last-modified-at-1.3.0/jekyll-last-modified-at.gemspec000066400000000000000000000016461365253500300241170ustar00rootroot00000000000000# frozen_string_literal: true require File.expand_path('lib/jekyll-last-modified-at/version.rb', __dir__) Gem::Specification.new do |s| s.name = 'jekyll-last-modified-at' s.version = Jekyll::LastModifiedAt::VERSION s.summary = 'A liquid tag for Jekyll to indicate the last time a file was modified.' s.authors = 'Garen J. Torikian' s.homepage = 'https://github.com/gjtorikian/jekyll-last-modified-at' s.license = 'MIT' s.files = Dir['lib/**/*.rb'] s.add_dependency 'jekyll', '>= 3.7', ' < 5.0' s.add_dependency 'posix-spawn', '~> 0.3.9' s.add_development_dependency 'rake' s.add_development_dependency 'rspec', '~> 3.4' s.add_development_dependency 'rubocop' s.add_development_dependency 'rubocop-performance' s.add_development_dependency 'rubocop-standard' s.add_development_dependency 'spork' end jekyll-last-modified-at-1.3.0/lib/000077500000000000000000000000001365253500300166765ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/lib/jekyll-last-modified-at.rb000066400000000000000000000010061365253500300236330ustar00rootroot00000000000000# frozen_string_literal: true module Jekyll module LastModifiedAt require 'jekyll-last-modified-at/tag' require 'jekyll-last-modified-at/hook' autoload :VERSION, 'jekyll-last-modified-at/version' autoload :Executor, 'jekyll-last-modified-at/executor' autoload :Determinator, 'jekyll-last-modified-at/determinator' autoload :Git, 'jekyll-last-modified-at/git' PATH_CACHE = {} # rubocop:disable Style/MutableConstant REPO_CACHE = {} # rubocop:disable Style/MutableConstant end end jekyll-last-modified-at-1.3.0/lib/jekyll-last-modified-at/000077500000000000000000000000001365253500300233115ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/lib/jekyll-last-modified-at/determinator.rb000066400000000000000000000045031365253500300263350ustar00rootroot00000000000000# frozen_string_literal: true module Jekyll module LastModifiedAt class Determinator attr_reader :site_source, :page_path attr_accessor :format def initialize(site_source, page_path, format = nil) @site_source = site_source @page_path = page_path @format = format || '%d-%b-%y' end def git return REPO_CACHE[site_source] unless REPO_CACHE[site_source].nil? REPO_CACHE[site_source] = Git.new(site_source) REPO_CACHE[site_source] end def formatted_last_modified_date return PATH_CACHE[page_path] unless PATH_CACHE[page_path].nil? last_modified = last_modified_at_time.strftime(@format) PATH_CACHE[page_path] = last_modified last_modified end def last_modified_at_time raise Errno::ENOENT, "#{absolute_path_to_article} does not exist!" unless File.exist? absolute_path_to_article Time.at(last_modified_at_unix.to_i) end def last_modified_at_unix if git.git_repo? last_commit_date = Executor.sh( 'git', '--git-dir', git.top_level_directory, 'log', '-n', '1', '--format="%ct"', '--', relative_path_from_git_dir )[/\d+/] # last_commit_date can be nil iff the file was not committed. last_commit_date.nil? || last_commit_date.empty? ? mtime(absolute_path_to_article) : last_commit_date else mtime(absolute_path_to_article) end end def to_s @to_s ||= formatted_last_modified_date end def to_liquid @to_liquid ||= last_modified_at_time end private def absolute_path_to_article @absolute_path_to_article ||= Jekyll.sanitized_path(site_source, @page_path) end def relative_path_from_git_dir return nil unless git.git_repo? @relative_path_from_git_dir ||= Pathname.new(absolute_path_to_article) .relative_path_from( Pathname.new(File.dirname(git.top_level_directory)) ).to_s end def mtime(file) File.mtime(file).to_i.to_s end end end end jekyll-last-modified-at-1.3.0/lib/jekyll-last-modified-at/executor.rb000066400000000000000000000013601365253500300254740ustar00rootroot00000000000000# frozen_string_literal: true require 'posix/spawn' module Jekyll module LastModifiedAt module Executor extend POSIX::Spawn def self.sh(*args) r, w = IO.pipe e, eo = IO.pipe pid = spawn(*args, :out => w, r => :close, :err => eo, e => :close) if pid.positive? w.close eo.close out = r.read err = e.read ::Process.waitpid(pid) "#{out} #{err}".strip if out end ensure [r, w, e, eo].each do |io| begin io.close rescue StandardError nil end end end end end end jekyll-last-modified-at-1.3.0/lib/jekyll-last-modified-at/git.rb000066400000000000000000000016651365253500300244310ustar00rootroot00000000000000# frozen_string_literal: true module Jekyll module LastModifiedAt class Git attr_reader :site_source def initialize(site_source) @site_source = site_source @is_git_repo = nil end def top_level_directory return nil unless git_repo? @top_level_directory ||= begin Dir.chdir(@site_source) do @top_level_directory = File.join(Executor.sh('git', 'rev-parse', '--show-toplevel'), '.git') end rescue StandardError '' end end def git_repo? return @is_git_repo unless @is_git_repo.nil? @is_git_repo = begin Dir.chdir(@site_source) do Executor.sh('git', 'rev-parse', '--is-inside-work-tree').eql? 'true' end rescue StandardError false end end end end end jekyll-last-modified-at-1.3.0/lib/jekyll-last-modified-at/hook.rb000066400000000000000000000012101365253500300245700ustar00rootroot00000000000000# frozen_string_literal: true module Jekyll module LastModifiedAt module Hook def self.add_determinator_proc proc { |item| format = item.site.config.dig('last-modified-at', 'date-format') item.data['last_modified_at'] = Determinator.new(item.site.source, item.path, format) } 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 end end end jekyll-last-modified-at-1.3.0/lib/jekyll-last-modified-at/tag.rb000066400000000000000000000012131365253500300244060ustar00rootroot00000000000000# frozen_string_literal: true module Jekyll module LastModifiedAt class Tag < Liquid::Tag def initialize(tag_name, format, tokens) super @format = format.empty? ? nil : format.strip end def render(context) site = context.registers[:site] format = @format || site.config.dig('last-modified-at', 'date-format') article_file = context.environments.first['page']['path'] Determinator.new(site.source, article_file, format) .formatted_last_modified_date end end end end Liquid::Template.register_tag('last_modified_at', Jekyll::LastModifiedAt::Tag) jekyll-last-modified-at-1.3.0/lib/jekyll-last-modified-at/version.rb000066400000000000000000000001451365253500300253230ustar00rootroot00000000000000# frozen_string_literal: true module Jekyll module LastModifiedAt VERSION = '1.3.0' end end jekyll-last-modified-at-1.3.0/script/000077500000000000000000000000001365253500300174345ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/script/bootstrap000077500000000000000000000000351365253500300213750ustar00rootroot00000000000000#! /bin/bash bundle install jekyll-last-modified-at-1.3.0/script/cibuild000077500000000000000000000001061365253500300207720ustar00rootroot00000000000000#! /bin/bash script/bootstrap > /dev/null 2>&1 bundle exec rake spec jekyll-last-modified-at-1.3.0/spec/000077500000000000000000000000001365253500300170625ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/spec/dev/000077500000000000000000000000001365253500300176405ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/spec/dev/.gitkeep000066400000000000000000000000001365253500300212570ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/spec/fixtures/000077500000000000000000000000001365253500300207335ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/spec/fixtures/_config.yml000066400000000000000000000000511365253500300230560ustar00rootroot00000000000000name: Your New Jekyll Site timezone: UTC jekyll-last-modified-at-1.3.0/spec/fixtures/_layouts/000077500000000000000000000000001365253500300225725ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/spec/fixtures/_layouts/last_modified_at.html000066400000000000000000000003051365253500300267450ustar00rootroot00000000000000 last-modified-at Article last updated on {% last_modified_at %} {{ content }} jekyll-last-modified-at-1.3.0/spec/fixtures/_layouts/last_modified_at_with_format.html000066400000000000000000000003351365253500300313530ustar00rootroot00000000000000 last-modified-at-with-format Article last updated on {% last_modified_at %Y:%B:%A:%d %} {{ content }} jekyll-last-modified-at-1.3.0/spec/fixtures/_posts/000077500000000000000000000000001365253500300222425ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/spec/fixtures/_posts/1984-03-06-command.md000066400000000000000000000001231365253500300252440ustar00rootroot00000000000000--- title: Testing Last Modified At layout: last_modified_at_with_format --- Boo. jekyll-last-modified-at-1.3.0/spec/fixtures/_posts/1984-03-06-last-modified-at-with-format.md000066400000000000000000000001231365253500300312100ustar00rootroot00000000000000--- title: Testing Last Modified At layout: last_modified_at_with_format --- Yay. jekyll-last-modified-at-1.3.0/spec/fixtures/_posts/1984-03-06-last-modified-at.md000066400000000000000000000001071365253500300267530ustar00rootroot00000000000000--- title: Testing Last Modified At layout: last_modified_at --- Yay. jekyll-last-modified-at-1.3.0/spec/fixtures/file.txt000066400000000000000000000000511365253500300224070ustar00rootroot00000000000000--- --- A page: {% last_modified_at %} jekyll-last-modified-at-1.3.0/spec/jekyll-last-modified-at/000077500000000000000000000000001365253500300234755ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/spec/jekyll-last-modified-at/determinator_spec.rb000066400000000000000000000045011365253500300275310ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' require 'tempfile' describe(Jekyll::LastModifiedAt::Determinator) do let(:site_source) { @fixtures_path } let(:page_path) { @fixtures_path.join('_posts').join('1984-03-06-command.md') } let(:mod_time) { Time.new(2019, 11, 17, 15, 35, 32, '+00:00') } subject { described_class.new(site_source.to_s, page_path.to_s) } it 'determines it is a git repo' do expect(subject.git.git_repo?).to eql(true) expect(subject.git.site_source).to end_with('spec/fixtures') expect(subject.git.top_level_directory).to end_with('/.git') end it 'knows the last modified date of the file in question' do expect(subject.formatted_last_modified_date).to eql('17-Nov-19') end it 'knows the last modified time (as a time object) of the file' do expect(subject.last_modified_at_time).to eql(mod_time) end it 'knows the last modified time of the file in question' do expect(subject.last_modified_at_unix).to eql('1574004932') end context 'not in a git repo' do let(:file) { Tempfile.new('some_file.txt') } let(:site_source) { File.dirname(file) } let(:page_path) { file.path } let(:mod_time) { Time.now } it 'determines it is not a git repo' do expect(subject.git.git_repo?).to eql(false) expect(subject.git.site_source).to eql(File.dirname(Tempfile.new)) expect(subject.git.top_level_directory).to eql(nil) end it 'uses the write time' do expect(subject.last_modified_at_time.to_i).to eql(mod_time.to_i) end it 'uses the write time for the date, too' do expect(subject.formatted_last_modified_date).to eql(mod_time.strftime('%d-%b-%y')) end end context '#to_s' do it 'returns the formatted date' do expect(subject.to_s).to eql('17-Nov-19') end end context '#to_liquid' do it 'returns a Time object' do expect(subject.to_liquid).to be_a(Time) end it 'returns the correct time' do expect(subject.to_liquid).to eql(mod_time) end end context 'without a format set' do it 'has a default format' do expect(subject.format).to eql('%d-%b-%y') end end context 'with a format set' do before(:each) { subject.format = '%Y-%m-%d' } it 'honors the custom format' do expect(subject.format).to eql('%Y-%m-%d') end end end jekyll-last-modified-at-1.3.0/spec/jekyll-last-modified-at/executor_spec.rb000066400000000000000000000003211365253500300266660ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe(Jekyll::LastModifiedAt::Executor) do it 'gets and strips the output' do expect(described_class.sh('echo', 'ohai')).to eql('ohai') end end jekyll-last-modified-at-1.3.0/spec/jekyll-last-modified-at/tag_spec.rb000066400000000000000000000007321365253500300256110ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe(Jekyll::LastModifiedAt::Tag) do let(:source) { @fixtures_path } let(:dest) { source.join('_site') } let(:site) do Site.new(Configuration::DEFAULTS.merge( 'source' => source.to_s, 'destination' => dest.to_s )) end subject { dest.join('file.txt').to_s } it 'understands happiness' do expect(File.read(subject)).to match(/12\-Sep\-14/) end end jekyll-last-modified-at-1.3.0/spec/plugins/000077500000000000000000000000001365253500300205435ustar00rootroot00000000000000jekyll-last-modified-at-1.3.0/spec/plugins/last_modified_at_spec.rb000066400000000000000000000027361365253500300254010ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe 'Last Modified At Tag' do context 'A committed post file' do def setup(file, layout) @post = setup_post(file) do_render(@post, layout) end it 'has last revised date' do setup('1984-03-06-last-modified-at.md', 'last_modified_at.html') expect(@post.output).to match(/Article last updated on 03-Jan-14/) end it 'passes along last revised date format' do setup('1984-03-06-last-modified-at-with-format.md', 'last_modified_at_with_format.html') expect(@post.output).to match(/Article last updated on 2014:January:Saturday:04/) end it 'ignores files that do not exist' do expect { setup('1984-03-06-what-the-eff.md', 'last_modified_at_with_format.html') }.to_not raise_error end it 'does not run arbitrary commands' do setup('1984-03-06-command.md|whoami>.gitkeep', 'last_modified_at_with_format.html') expect(File.exist?('.bogus')).to be false end end context 'An uncommitted post file' do before(:all) do cheater_file = '1984-03-06-last-modified-at.md' uncommitted_file = '1992-09-11-last-modified-at.md' duplicate_post(cheater_file, uncommitted_file) @post = setup_post(uncommitted_file) do_render(@post, 'last_modified_at.html') end it 'has last revised date' do expect(@post.output).to match(Regexp.new("Article last updated on #{Time.new.utc.strftime('%d-%b-%y')}")) end end end jekyll-last-modified-at-1.3.0/spec/spec_helper.rb000066400000000000000000000036301365253500300217020ustar00rootroot00000000000000# frozen_string_literal: true require 'spork' require 'rspec' Spork.prefork do # Loading more in this block will cause your tests to run faster. However, # if you change any configuration or code from libraries loaded here, you'll # need to restart spork for it take effect. require 'jekyll' require 'liquid' include Jekyll end Spork.each_run do # This code will be run each time you run your specs. require File.expand_path('lib/jekyll-last-modified-at.rb') end RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end config.before(:all) do Jekyll.logger.log_level = :error # original_stderr = $stderr # original_stdout = $stdout @fixtures_path = Pathname.new(__FILE__).parent.join('fixtures') @dest = @fixtures_path.join('_site') @posts_src = File.join(@fixtures_path, '_posts') @layouts_src = File.join(@fixtures_path, '_layouts') $stderr = File.new(File.join(File.dirname(__FILE__), 'dev', 'err.txt'), 'w') $stdout = File.new(File.join(File.dirname(__FILE__), 'dev', 'out.txt'), 'w') @site = Jekyll::Site.new(Jekyll.configuration( 'source' => @fixtures_path.to_s, 'destination' => @dest.to_s )) @dest.rmtree if @dest.exist? @site.process end def post_path(file) File.join(@posts_src, file) end def duplicate_post(source, destination) File.open(post_path(destination), 'w') do |f| f.puts(File.read(post_path(source))) end end def setup_post(file) Document.new(@site.in_source_dir(File.join('_posts', file)), site: @site, collection: @site.posts).tap(&:read) end def do_render(post, layout) @site.layouts = { layout.sub(/\.html/, '') => Layout.new(@site, @layouts_src, layout) } post.output = Renderer.new(@site, post, @site.site_payload).run end end