├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── automerge.yml │ ├── lint.yml │ ├── tag_and_release.yml │ └── test.yml ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── .vscode └── settings.json ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── jekyll-last-modified-at.gemspec ├── lib ├── jekyll-last-modified-at.rb └── jekyll-last-modified-at │ ├── determinator.rb │ ├── executor.rb │ ├── git.rb │ ├── hook.rb │ ├── tag.rb │ └── version.rb ├── script ├── bootstrap └── cibuild └── spec ├── dev └── .gitkeep ├── fixtures ├── _config.yml ├── _layouts │ ├── last_modified_at.html │ └── last_modified_at_with_format.html ├── _posts │ ├── 1984-03-06-command.md │ ├── 1984-03-06-last-modified-at-with-format.md │ └── 1984-03-06-last-modified-at.md └── file.txt ├── jekyll-last-modified-at ├── determinator_spec.rb ├── executor_spec.rb └── tag_spec.rb ├── plugins └── last_modified_at_spec.rb └── spec_helper.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: gjtorikian 4 | # patreon: gjtorikian 5 | # open_collective: garen-torikian 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | # issuehunt: gjtorikian 11 | #otechie: # Replace with a single Otechie username 12 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | day: monday 8 | time: "09:00" 9 | timezone: "Etc/UTC" 10 | groups: 11 | github-actions: 12 | patterns: 13 | - "*" 14 | open-pull-requests-limit: 10 15 | 16 | - package-ecosystem: bundler 17 | directory: "/" 18 | schedule: 19 | interval: weekly 20 | day: monday 21 | time: "09:00" 22 | timezone: "Etc/UTC" 23 | open-pull-requests-limit: 10 24 | groups: 25 | bundler-dependencies: 26 | patterns: 27 | - "*" 28 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: "Bot auto-{approve,merge}" 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request_target: 6 | 7 | permissions: 8 | pull-requests: write 9 | contents: write 10 | 11 | jobs: 12 | dependabot: 13 | uses: yettoapp/actions/.github/workflows/automerge_dependabot.yml@main 14 | secrets: inherit 15 | with: 16 | automerge: true 17 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "**/*.rb" 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Set up Ruby 18 | uses: yettoapp/actions/setup-languages@main 19 | with: 20 | ruby: true 21 | 22 | - name: Rubocop 23 | run: bundle exec rake rubocop 24 | -------------------------------------------------------------------------------- /.github/workflows/tag_and_release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - "lib/jekyll-last-modified-at/version.rb" 10 | pull_request_target: 11 | types: 12 | - closed 13 | 14 | jobs: 15 | ruby: 16 | uses: yettoapp/actions/.github/workflows/ruby_gem_release.yml@main 17 | secrets: 18 | rubygems_api_key: ${{ secrets.RUBYGEMS_API_BOT_KEY }} 19 | gh_token: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | gem_name: jekyll-last-modified-at 22 | version_filepath: lib/jekyll-last-modified-at/version.rb 23 | prepare: ${{ github.event_name == 'push' }} 24 | release: ${{ github.event_name == 'workflow_dispatch' || ((github.event.pull_request.merged == true) && (contains(github.event.pull_request.labels.*.name, 'release'))) }} 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - if: "${{ contains(github.event.pull_request.title, '[skip test]') }}" 15 | name: Skip test 16 | shell: bash 17 | run: | 18 | echo "Skipping test workflow because commit message contains `[skip test]`" 19 | exit 0 20 | 21 | - uses: actions/checkout@v4 22 | 23 | - name: Set up Ruby 24 | uses: yettoapp/actions/setup-languages@main 25 | with: 26 | ruby: true 27 | 28 | - name: Run tests 29 | run: bundle exec rake test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | _site/ 3 | Gemfile.lock 4 | spec/fixtures/_posts/1992-09-11-last-modified-at.md 5 | spec/fixtures/.jekyll-metadata 6 | spec/fixtures/.jekyll-cache 7 | spec/dev/out.txt 8 | spec/dev/err.txt 9 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-standard: 3 | - config/default.yml 4 | - config/minitest.yml 5 | 6 | inherit_mode: 7 | merge: 8 | - Exclude 9 | 10 | AllCops: 11 | Exclude: 12 | - test/progit/**/* 13 | - "pkg/**/*" 14 | - "ext/**/*" 15 | - "vendor/**/*" 16 | - "tmp/**/*" 17 | - "test/progit/**/*" 18 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.1 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[ruby]": { 3 | "editor.defaultFormatter": "Shopify.ruby-lsp" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [v1.3.2] - 04-06-2024 2 | ## What's Changed 3 | * Modernize project by @gjtorikian in https://github.com/gjtorikian/jekyll-last-modified-at/pull/99 4 | * lint by @gjtorikian in https://github.com/gjtorikian/jekyll-last-modified-at/pull/100 5 | * :gem: 1.3.1 by @gjtorikian in https://github.com/gjtorikian/jekyll-last-modified-at/pull/101 6 | * :gem: 1.3.2 by @gjtorikian in https://github.com/gjtorikian/jekyll-last-modified-at/pull/102 7 | 8 | 9 | **Full Changelog**: https://github.com/gjtorikian/jekyll-last-modified-at/compare/v1.3.0...v1.3.2 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | 7 | gem "jekyll", ENV["JEKYLL_VERSION"] if ENV["JEKYLL_VERSION"] 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Garen J. Torikian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Last Modified At Plugin 2 | 3 | A liquid tag for Jekyll to indicate the last time a file was modified. 4 | 5 | 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. 6 | 7 | ## Setting up 8 | 9 | Open your Gemfile in your Jekyll root folder and add the following: 10 | 11 | ``` ruby 12 | group :jekyll_plugins do 13 | gem "jekyll-last-modified-at" 14 | end 15 | ``` 16 | 17 | Add the following to your site's `_config.yml` file 18 | 19 | ```yml 20 | plugins: 21 | - jekyll-last-modified-at 22 | 23 | # Optional. The default date format, used if none is specified in the tag. 24 | last-modified-at: 25 | date-format: '%d-%b-%y' 26 | ``` 27 | 28 | ## Usage 29 | 30 | There are a few ways to use this gem. 31 | 32 | You can place the following tag somewhere within your layout: 33 | 34 | ``` liquid 35 | {% last_modified_at %} 36 | ``` 37 | 38 | By default, this creates a time format matching `"%d-%b-%y"` (like "04-Jan-14"). 39 | 40 | You can also choose to pass along your own time format. For example: 41 | 42 | ```liquid 43 | {% last_modified_at %Y:%B:%A:%d:%S:%R %} 44 | ``` 45 | That produces "2014:January:Saturday:04." 46 | 47 | You can also call the method directly on a Jekyll "object," like so: 48 | 49 | ``` liquid 50 | {{ page.last_modified_at }} 51 | ``` 52 | 53 | To format such a time, you'll need to rely on Liquid's `date` filter: 54 | 55 | ``` liquid 56 | {{ page.last_modified_at | date: '%Y:%B:%A:%d:%S:%R' }} 57 | ``` 58 | 59 | (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.) 60 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler" 4 | Bundler::GemHelper.install_tasks 5 | 6 | require "rspec/core/rake_task" 7 | 8 | RSpec::Core::RakeTask.new(:spec) 9 | 10 | desc "Test the project" 11 | task :test do 12 | Rake::Task["spec"].invoke 13 | end 14 | 15 | require "rubocop/rake_task" 16 | 17 | RuboCop::RakeTask.new(:rubocop) 18 | 19 | require "bundler/gem_tasks" 20 | require "rubygems/package_task" 21 | GEMSPEC = Bundler.load_gemspec("jekyll-last-modified-at.gemspec") 22 | gem_path = Gem::PackageTask.new(GEMSPEC).define 23 | desc "Package the ruby gem" 24 | task "package" => [gem_path] 25 | -------------------------------------------------------------------------------- /jekyll-last-modified-at.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path("lib/jekyll-last-modified-at/version.rb", __dir__) 4 | Gem::Specification.new do |s| 5 | s.name = "jekyll-last-modified-at" 6 | s.version = Jekyll::LastModifiedAt::VERSION 7 | s.summary = "A liquid tag for Jekyll to indicate the last time a file was modified." 8 | s.authors = "Garen J. Torikian" 9 | s.homepage = "https://github.com/gjtorikian/jekyll-last-modified-at" 10 | s.license = "MIT" 11 | s.files = Dir["lib/**/*.rb"] 12 | 13 | s.add_dependency("jekyll", ">= 3.7", " < 5.0") 14 | 15 | s.add_development_dependency("rake") 16 | s.add_development_dependency("rspec", "~> 3.4") 17 | s.add_development_dependency("rubocop") 18 | s.add_development_dependency("rubocop-standard") 19 | s.add_development_dependency("spork") 20 | end 21 | -------------------------------------------------------------------------------- /lib/jekyll-last-modified-at.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module LastModifiedAt 5 | require "jekyll-last-modified-at/tag" 6 | require "jekyll-last-modified-at/hook" 7 | 8 | autoload :VERSION, "jekyll-last-modified-at/version" 9 | autoload :Executor, "jekyll-last-modified-at/executor" 10 | autoload :Determinator, "jekyll-last-modified-at/determinator" 11 | autoload :Git, "jekyll-last-modified-at/git" 12 | 13 | PATH_CACHE = {} 14 | REPO_CACHE = {} 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/jekyll-last-modified-at/determinator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module LastModifiedAt 5 | class Determinator 6 | attr_reader :site_source, :page_path 7 | attr_accessor :format 8 | 9 | def initialize(site_source, page_path, format = nil) 10 | @site_source = site_source 11 | @page_path = page_path 12 | @format = format || "%d-%b-%y" 13 | end 14 | 15 | def git 16 | return REPO_CACHE[site_source] unless REPO_CACHE[site_source].nil? 17 | 18 | REPO_CACHE[site_source] = Git.new(site_source) 19 | REPO_CACHE[site_source] 20 | end 21 | 22 | def formatted_last_modified_date 23 | return PATH_CACHE[page_path] unless PATH_CACHE[page_path].nil? 24 | 25 | last_modified = last_modified_at_time.strftime(@format) 26 | PATH_CACHE[page_path] = last_modified 27 | last_modified 28 | end 29 | 30 | def last_modified_at_time 31 | raise Errno::ENOENT, "#{absolute_path_to_article} does not exist!" unless File.exist?(absolute_path_to_article) 32 | 33 | Time.at(last_modified_at_unix.to_i) 34 | end 35 | 36 | def last_modified_at_unix 37 | if git.git_repo? 38 | last_commit_date = Executor.sh( 39 | "git", 40 | "--git-dir", 41 | git.top_level_directory, 42 | "log", 43 | "-n", 44 | "1", 45 | '--format="%ct"', 46 | "--", 47 | relative_path_from_git_dir, 48 | )[/\d+/] 49 | # last_commit_date can be nil iff the file was not committed. 50 | last_commit_date.nil? || last_commit_date.empty? ? mtime(absolute_path_to_article) : last_commit_date 51 | else 52 | mtime(absolute_path_to_article) 53 | end 54 | end 55 | 56 | def to_s 57 | @to_s ||= formatted_last_modified_date 58 | end 59 | 60 | def to_liquid 61 | @to_liquid ||= last_modified_at_time 62 | end 63 | 64 | private 65 | 66 | def absolute_path_to_article 67 | @absolute_path_to_article ||= Jekyll.sanitized_path(site_source, @page_path) 68 | end 69 | 70 | def relative_path_from_git_dir 71 | return unless git.git_repo? 72 | 73 | @relative_path_from_git_dir ||= Pathname.new(absolute_path_to_article) 74 | .relative_path_from( 75 | Pathname.new(File.dirname(git.top_level_directory)), 76 | ).to_s 77 | end 78 | 79 | def mtime(file) 80 | File.mtime(file).to_i.to_s 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/jekyll-last-modified-at/executor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "open3" 4 | 5 | module Jekyll 6 | module LastModifiedAt 7 | module Executor 8 | class << self 9 | def sh(*args) 10 | stdout_str, stderr_str, status = Open3.capture3(*args) 11 | "#{stdout_str} #{stderr_str}".strip if status.success? 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/jekyll-last-modified-at/git.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module LastModifiedAt 5 | class Git 6 | attr_reader :site_source 7 | 8 | def initialize(site_source) 9 | @site_source = site_source 10 | @is_git_repo = nil 11 | end 12 | 13 | def top_level_directory 14 | return unless git_repo? 15 | 16 | @top_level_directory ||= begin 17 | Dir.chdir(@site_source) do 18 | @top_level_directory = File.join(Executor.sh("git", "rev-parse", "--show-toplevel"), ".git") 19 | end 20 | rescue StandardError 21 | "" 22 | end 23 | end 24 | 25 | def git_repo? 26 | return @is_git_repo unless @is_git_repo.nil? 27 | 28 | @is_git_repo = begin 29 | Dir.chdir(@site_source) do 30 | Executor.sh("git", "rev-parse", "--is-inside-work-tree").eql?("true") 31 | end 32 | rescue StandardError 33 | false 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/jekyll-last-modified-at/hook.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module LastModifiedAt 5 | module Hook 6 | class << self 7 | def add_determinator_proc 8 | proc { |item| 9 | format = item.site.config.dig("last-modified-at", "date-format") 10 | item.data["last_modified_at"] = Determinator.new( 11 | item.site.source, 12 | item.path, 13 | format, 14 | ) 15 | } 16 | end 17 | end 18 | 19 | Jekyll::Hooks.register(:posts, :post_init, &Hook.add_determinator_proc) 20 | Jekyll::Hooks.register(:pages, :post_init, &Hook.add_determinator_proc) 21 | Jekyll::Hooks.register(:documents, :post_init, &Hook.add_determinator_proc) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/jekyll-last-modified-at/tag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module LastModifiedAt 5 | class Tag < Liquid::Tag 6 | def initialize(tag_name, format, tokens) 7 | super 8 | @format = format.empty? ? nil : format.strip 9 | end 10 | 11 | def render(context) 12 | site = context.registers[:site] 13 | format = @format || site.config.dig("last-modified-at", "date-format") 14 | article_file = context.environments.first["page"]["path"] 15 | Determinator.new(site.source, article_file, format) 16 | .formatted_last_modified_date 17 | end 18 | end 19 | end 20 | end 21 | 22 | Liquid::Template.register_tag("last_modified_at", Jekyll::LastModifiedAt::Tag) 23 | -------------------------------------------------------------------------------- /lib/jekyll-last-modified-at/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module LastModifiedAt 5 | VERSION = "1.3.2" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | bundle install 4 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | script/bootstrap > /dev/null 2>&1 4 | bundle exec rake spec 5 | -------------------------------------------------------------------------------- /spec/dev/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gjtorikian/jekyll-last-modified-at/3052cbe5fdf550dd1e85ed8eb7ec7a5595cd9f1d/spec/dev/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/_config.yml: -------------------------------------------------------------------------------- 1 | name: Your New Jekyll Site 2 | timezone: UTC 3 | -------------------------------------------------------------------------------- /spec/fixtures/_layouts/last_modified_at.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | last-modified-at 4 | 5 | 6 | Article last updated on {% last_modified_at %} 7 | 8 | {{ content }} 9 | 10 | 11 | -------------------------------------------------------------------------------- /spec/fixtures/_layouts/last_modified_at_with_format.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | last-modified-at-with-format 4 | 5 | 6 | Article last updated on {% last_modified_at %Y:%B:%A:%d %} 7 | 8 | {{ content }} 9 | 10 | 11 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/1984-03-06-command.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Testing Last Modified At 3 | layout: last_modified_at_with_format 4 | --- 5 | 6 | Boo. 7 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/1984-03-06-last-modified-at-with-format.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Testing Last Modified At 3 | layout: last_modified_at_with_format 4 | --- 5 | 6 | Yay. 7 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/1984-03-06-last-modified-at.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Testing Last Modified At 3 | layout: last_modified_at 4 | --- 5 | 6 | Yay. 7 | -------------------------------------------------------------------------------- /spec/fixtures/file.txt: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | A page: 5 | 6 | {% last_modified_at %} 7 | -------------------------------------------------------------------------------- /spec/jekyll-last-modified-at/determinator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | require "tempfile" 5 | 6 | describe(Jekyll::LastModifiedAt::Determinator) do 7 | subject { described_class.new(site_source.to_s, page_path.to_s) } 8 | 9 | let(:site_source) { @fixtures_path } 10 | let(:page_path) { @fixtures_path.join("_posts").join("1984-03-06-command.md") } 11 | let(:mod_time) { Time.new(2019, 11, 17, 15, 35, 32, "+00:00") } 12 | 13 | it "determines it is a git repo" do 14 | expect(subject.git.git_repo?).to(be(true)) 15 | expect(subject.git.site_source).to(end_with("spec/fixtures")) 16 | expect(subject.git.top_level_directory).to(end_with("/.git")) 17 | end 18 | 19 | it "knows the last modified date of the file in question" do 20 | expect(subject.formatted_last_modified_date).to(eql("17-Nov-19")) 21 | end 22 | 23 | it "knows the last modified time (as a time object) of the file" do 24 | expect(subject.last_modified_at_time).to(eql(mod_time)) 25 | end 26 | 27 | it "knows the last modified time of the file in question" do 28 | expect(subject.last_modified_at_unix).to(eql("1574004932")) 29 | end 30 | 31 | context "not in a git repo" do 32 | let(:file) { Tempfile.new("some_file.txt") } 33 | let(:site_source) { File.dirname(file) } 34 | let(:page_path) { file.path } 35 | let(:mod_time) { Time.now } 36 | 37 | it "determines it is not a git repo" do 38 | expect(subject.git.git_repo?).to(be(false)) 39 | expect(subject.git.site_source).to(eql(File.dirname(Tempfile.new))) 40 | expect(subject.git.top_level_directory).to(be_nil) 41 | end 42 | 43 | it "uses the write time" do 44 | expect(subject.last_modified_at_time.to_i).to(eql(mod_time.to_i)) 45 | end 46 | 47 | it "uses the write time for the date, too" do 48 | expect(subject.formatted_last_modified_date).to(eql(mod_time.strftime("%d-%b-%y"))) 49 | end 50 | end 51 | 52 | describe "#to_s" do 53 | it "returns the formatted date" do 54 | expect(subject.to_s).to(eql("17-Nov-19")) 55 | end 56 | end 57 | 58 | describe "#to_liquid" do 59 | it "returns a Time object" do 60 | expect(subject.to_liquid).to(be_a(Time)) 61 | end 62 | 63 | it "returns the correct time" do 64 | expect(subject.to_liquid).to(eql(mod_time)) 65 | end 66 | end 67 | 68 | context "without a format set" do 69 | it "has a default format" do 70 | expect(subject.format).to(eql("%d-%b-%y")) 71 | end 72 | end 73 | 74 | context "with a format set" do 75 | before { subject.format = "%Y-%m-%d" } 76 | 77 | it "honors the custom format" do 78 | expect(subject.format).to(eql("%Y-%m-%d")) 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/jekyll-last-modified-at/executor_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | describe(Jekyll::LastModifiedAt::Executor) do 6 | it "gets and strips the output" do 7 | expect(described_class.sh("echo", "ohai")).to(eql("ohai")) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/jekyll-last-modified-at/tag_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | describe(Jekyll::LastModifiedAt::Tag) do 6 | subject { dest.join("file.txt").to_s } 7 | 8 | let(:source) { @fixtures_path } 9 | let(:dest) { source.join("_site") } 10 | let(:site) do 11 | Site.new(Configuration::DEFAULTS.merge( 12 | "source" => source.to_s, 13 | "destination" => dest.to_s, 14 | )) 15 | end 16 | 17 | it "understands happiness" do 18 | expect(File.read(subject)).to(match(/12\-Sep\-14/)) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/plugins/last_modified_at_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | describe "Last Modified At Tag" do 6 | context "A committed post file" do 7 | def setup(file, layout) 8 | @post = setup_post(file) 9 | do_render(@post, layout) 10 | end 11 | 12 | it "has last revised date" do 13 | setup("1984-03-06-last-modified-at.md", "last_modified_at.html") 14 | expect(@post.output).to(match(/Article last updated on 03-Jan-14/)) 15 | end 16 | 17 | it "passes along last revised date format" do 18 | setup("1984-03-06-last-modified-at-with-format.md", "last_modified_at_with_format.html") 19 | expect(@post.output).to(match(/Article last updated on 2014:January:Saturday:04/)) 20 | end 21 | 22 | it "ignores files that do not exist" do 23 | expect { setup("1984-03-06-what-the-eff.md", "last_modified_at_with_format.html") }.not_to(raise_error) 24 | end 25 | 26 | it "does not run arbitrary commands" do 27 | setup("1984-03-06-command.md|whoami>.gitkeep", "last_modified_at_with_format.html") 28 | expect(File.exist?(".bogus")).to(be(false)) 29 | end 30 | end 31 | 32 | context "An uncommitted post file" do 33 | before(:all) do 34 | cheater_file = "1984-03-06-last-modified-at.md" 35 | uncommitted_file = "1992-09-11-last-modified-at.md" 36 | duplicate_post(cheater_file, uncommitted_file) 37 | @post = setup_post(uncommitted_file) 38 | do_render(@post, "last_modified_at.html") 39 | end 40 | 41 | it "has last revised date" do 42 | expect(@post.output).to(match(Regexp.new("Article last updated on #{Time.new.utc.strftime("%d-%b-%y")}"))) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spork" 4 | require "rspec" 5 | 6 | Spork.prefork do 7 | # Loading more in this block will cause your tests to run faster. However, 8 | # if you change any configuration or code from libraries loaded here, you'll 9 | # need to restart spork for it take effect. 10 | 11 | require "jekyll" 12 | require "liquid" 13 | 14 | include Jekyll 15 | end 16 | 17 | Spork.each_run do 18 | # This code will be run each time you run your specs. 19 | require File.expand_path("lib/jekyll-last-modified-at.rb") 20 | end 21 | 22 | RSpec.configure do |config| 23 | config.expect_with(:rspec) do |c| 24 | c.syntax = :expect 25 | end 26 | 27 | config.before(:all) do 28 | Jekyll.logger.log_level = :error 29 | 30 | # original_stderr = $stderr 31 | # original_stdout = $stdout 32 | 33 | @fixtures_path = Pathname.new(__FILE__).parent.join("fixtures") 34 | @dest = @fixtures_path.join("_site") 35 | @posts_src = File.join(@fixtures_path, "_posts") 36 | @layouts_src = File.join(@fixtures_path, "_layouts") 37 | 38 | $stderr = File.new(File.join(File.dirname(__FILE__), "dev", "err.txt"), "w") 39 | $stdout = File.new(File.join(File.dirname(__FILE__), "dev", "out.txt"), "w") 40 | 41 | @site = Jekyll::Site.new(Jekyll.configuration( 42 | "source" => @fixtures_path.to_s, 43 | "destination" => @dest.to_s, 44 | )) 45 | 46 | @dest.rmtree if @dest.exist? 47 | @site.process 48 | end 49 | 50 | def post_path(file) 51 | File.join(@posts_src, file) 52 | end 53 | 54 | def duplicate_post(source, destination) 55 | File.open(post_path(destination), "w") do |f| 56 | f.puts(File.read(post_path(source))) 57 | end 58 | end 59 | 60 | def setup_post(file) 61 | Document.new( 62 | @site.in_source_dir(File.join("_posts", file)), 63 | site: @site, 64 | collection: @site.posts, 65 | ).tap(&:read) 66 | end 67 | 68 | def do_render(post, layout) 69 | @site.layouts = { layout.sub(".html", "") => Layout.new(@site, @layouts_src, layout) } 70 | post.output = Renderer.new(@site, post, @site.site_payload).run 71 | end 72 | end 73 | --------------------------------------------------------------------------------