From da241c1f6a56db3fc013cb86bc54c3094f34f664 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:14:18 +0000 Subject: [PATCH 1/6] Initial plan From 8f5b91416fe7c6b09694d8d95b1fbf7d8b2b4080 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:27:16 +0000 Subject: [PATCH 2/6] Implement "only" option for timeago with all supported units and comprehensive tests Co-authored-by: markets <576701+markets@users.noreply.github.com> --- bin/timeago | 12 ++++++ lib/jekyll-timeago/core.rb | 52 +++++++++++++++++++++++-- spec/jekyll-timeago_spec.rb | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 3 deletions(-) diff --git a/bin/timeago b/bin/timeago index 29a8581..559d1e9 100755 --- a/bin/timeago +++ b/bin/timeago @@ -16,6 +16,7 @@ help_message = <<~HELP_MESSAGE --console, -c Starts an interactive IRB session with jekyll-timeago included --locale, -l Uses the provided locale --style, -s Uses the provided style (short) + --only, -o Accumulates time in specified unit (years, months, weeks, days) HELP_MESSAGE if ARGV.empty? || ARGV.include?("--help") || ARGV.include?("-h") @@ -53,6 +54,17 @@ else options[:style] = style end + # Handle only option + custom_only = "--only" if ARGV.include?("--only") + custom_only = "-o" if ARGV.include?("-o") + + if custom_only + index = ARGV.index(custom_only) + only = ARGV.delete_at(index + 1) + ARGV.delete_at(index) + options[:only] = only + end + ARGV << options if !options.empty? begin diff --git a/lib/jekyll-timeago/core.rb b/lib/jekyll-timeago/core.rb index 3c6aff8..ebba68d 100644 --- a/lib/jekyll-timeago/core.rb +++ b/lib/jekyll-timeago/core.rb @@ -17,6 +17,9 @@ module Core # Available styles STYLES = %w(default short array) + # Available "only" options + ONLY_OPTIONS = %w(years months weeks days) + def timeago(from, to = Date.today, options = {}) if to.is_a?(Hash) options = to @@ -30,6 +33,7 @@ def timeago(from, to = Date.today, options = {}) @depth = validate_depth(@options[:depth] || @options["depth"]) @style = validate_style(@options[:style] || @options["style"]) @threshold = validate_threshold(@options[:threshold] || @options["threshold"]) + @only = validate_only(@options[:only] || @options["only"]) time_ago_to_now end @@ -53,12 +57,22 @@ def validate_style(style) STYLES.include?(style) ? style : nil end + def validate_only(only) + return nil if only.nil? + only = only.to_s + ONLY_OPTIONS.include?(only) ? only : nil + end + def time_ago_to_now days_passed = (@to - @from).to_i - return t(:today) if days_passed == 0 - return t(:yesterday) if days_passed == 1 - return t(:tomorrow) if days_passed == -1 + # Handle special cases only if "only" option is not specified + # or if days_passed is 0 (today should be "today" regardless of only option) + if !@only || days_passed == 0 + return t(:today) if days_passed == 0 + return t(:yesterday) if days_passed == 1 && !@only + return t(:tomorrow) if days_passed == -1 && !@only + end past_or_future = @from < @to ? :past : :future slots = build_time_ago_slots(days_passed.abs) @@ -85,6 +99,11 @@ def translate_unit(unit, count) # Builds time ranges with natural unit conversions: ['1 month', '5 days'] def build_time_ago_slots(days_passed) + # If "only" option is specified, calculate total time in that unit + if @only + return build_only_slots(days_passed) + end + # Calculate components with natural unit conversions components = calculate_natural_components(days_passed) @@ -95,6 +114,33 @@ def build_time_ago_slots(days_passed) selected.map { |unit, count| translate_unit(unit, count) } end + # Build time slots when "only" option is specified + def build_only_slots(days_passed) + unit = @only.to_sym + count = calculate_total_in_unit(days_passed, unit) + [translate_unit(unit, count)] + end + + # Calculate total time in specified unit + def calculate_total_in_unit(days_passed, unit) + case unit + when :days + days_passed + when :weeks + # Ensure minimum of 1 week if days_passed > 0 + return 1 if days_passed > 0 && days_passed < 7 + (days_passed / 7.0).round + when :months + # Ensure minimum of 1 month if days_passed > 0 + return 1 if days_passed > 0 && days_passed < 30 + (days_passed / 30.0).round + when :years + # Ensure minimum of 1 year if days_passed > 0 + return 1 if days_passed > 0 && days_passed < 365 + (days_passed / 365.0).round + end + end + def calculate_natural_components(days_passed) years = days_passed / 365 remaining_days = days_passed % 365 diff --git a/spec/jekyll-timeago_spec.rb b/spec/jekyll-timeago_spec.rb index 639f0c3..1b144a4 100644 --- a/spec/jekyll-timeago_spec.rb +++ b/spec/jekyll-timeago_spec.rb @@ -130,6 +130,73 @@ expect(timeago(sample_date.prev_day(160), sample_date, style: :array)).to eq(['5 months', '1 week']) expect(timeago(sample_date.prev_day(160), sample_date, style: :array, locale: :es)).to eq(['5 meses', '1 semana']) end + + it 'allows "only" option to accumulate time into single unit' do + # Test "only: :days" + expect(timeago(sample_date.prev_day(7), sample_date, only: :days)).to eq('7 days ago') + expect(timeago(sample_date.prev_day(7), sample_date, "only" => "days")).to eq('7 days ago') + expect(timeago(sample_date.prev_day(30), sample_date, only: :days)).to eq('30 days ago') + + # Test "only: :weeks" + expect(timeago(sample_date.prev_day(7), sample_date, only: :weeks)).to eq('1 week ago') + expect(timeago(sample_date.prev_day(14), sample_date, only: :weeks)).to eq('2 weeks ago') + expect(timeago(sample_date.prev_day(30), sample_date, only: :weeks)).to eq('4 weeks ago') + expect(timeago(sample_date.prev_day(365), sample_date, only: :weeks)).to eq('52 weeks ago') + + # Test "only: :months" + expect(timeago(sample_date.prev_day(30), sample_date, only: :months)).to eq('1 month ago') + expect(timeago(sample_date.prev_day(60), sample_date, only: :months)).to eq('2 months ago') + expect(timeago(sample_date.prev_day(365), sample_date, only: :months)).to eq('12 months ago') + + # Test "only: :years" + expect(timeago(sample_date.prev_day(365), sample_date, only: :years)).to eq('1 year ago') + expect(timeago(sample_date.prev_day(730), sample_date, only: :years)).to eq('2 years ago') + expect(timeago(sample_date.prev_day(1000), sample_date, only: :years)).to eq('3 years ago') + end + + it 'handles special cases with "only" option' do + # Today should always return "today" regardless of only option + expect(timeago(sample_date, sample_date, only: :days)).to eq('today') + expect(timeago(sample_date, sample_date, only: :weeks)).to eq('today') + expect(timeago(sample_date, sample_date, only: :months)).to eq('today') + + # Yesterday and tomorrow should use only option when specified + expect(timeago(sample_date.prev_day, sample_date, only: :days)).to eq('1 day ago') + expect(timeago(sample_date.next_day, sample_date, only: :days)).to eq('in 1 day') + + # Small values should round up to minimum 1 of the specified unit + expect(timeago(sample_date.prev_day(1), sample_date, only: :weeks)).to eq('1 week ago') + expect(timeago(sample_date.prev_day(3), sample_date, only: :weeks)).to eq('1 week ago') + expect(timeago(sample_date.prev_day(7), sample_date, only: :months)).to eq('1 month ago') + end + + it 'allows "only" option with future dates' do + expect(timeago(sample_date.next_day(7), sample_date, only: :weeks)).to eq('in 1 week') + expect(timeago(sample_date.next_day(30), sample_date, only: :months)).to eq('in 1 month') + expect(timeago(sample_date.next_day(365), sample_date, only: :years)).to eq('in 1 year') + end + + it 'allows "only" option with different styles' do + # Test with short style + expect(timeago(sample_date.prev_day(365), sample_date, only: :weeks, style: :short)).to eq('52w ago') + expect(timeago(sample_date.prev_day(30), sample_date, only: :months, style: :short)).to eq('1mo ago') + + # Test with array style + expect(timeago(sample_date.prev_day(365), sample_date, only: :weeks, style: :array)).to eq(['52 weeks']) + expect(timeago(sample_date.prev_day(30), sample_date, only: :months, style: :array)).to eq(['1 month']) + end + + it 'allows "only" option with different locales' do + expect(timeago(sample_date.prev_day(30), sample_date, only: :weeks, locale: :es)).to eq('hace 4 semanas') + expect(timeago(sample_date.prev_day(365), sample_date, only: :months, locale: :fr)).to eq('il y a environ 12 mois') + end + + it 'ignores invalid "only" values' do + # Invalid "only" values should be ignored and behave like normal + expect(timeago(sample_date.prev_day(365), sample_date, only: :invalid)).to eq('1 year ago') + expect(timeago(sample_date.prev_day(365), sample_date, only: 'invalid')).to eq('1 year ago') + expect(timeago(sample_date.prev_day(365), sample_date, only: nil)).to eq('1 year ago') + end end context 'CLI' do @@ -167,5 +234,16 @@ expect(`bin/timeago 2016-1-1 2018-1-1 -l fr -s short`).to match("il y a environ 2a") expect(`bin/timeago 2016-1-1 2018-1-1 --locale ru --style short`).to match("2г и 1д назад") end + + it 'with only option' do + expect(`bin/timeago 2016-1-1 2018-1-1 --only weeks`).to match("104 weeks ago") + expect(`bin/timeago 2016-1-1 2018-1-1 -o months`).to match("24 months ago") + expect(`bin/timeago 2016-1-1 2016-2-1 --only days`).to match("31 days ago") + end + + it 'with combined options including only' do + expect(`bin/timeago 2016-1-1 2018-1-1 -l fr --only months`).to match("il y a environ 24 mois") + expect(`bin/timeago 2016-1-1 2018-1-1 --only weeks -s short`).to match("104w ago") + end end end From b312d17fafbe0e9b24de9fb46a030dafad82602e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:28:44 +0000 Subject: [PATCH 3/6] Add comprehensive documentation for new "only" option in README Co-authored-by: markets <576701+markets@users.noreply.github.com> --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index efbfb8c..9075a78 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,30 @@ Use `:array` style for structured data: => ["5 months", "1 week"] ``` +#### `only` + +Use the `only` option to accumulate all time into a single unit. Supported values are `:years`, `:months`, `:weeks`, and `:days`: + +```ruby +>> timeago(Date.today.prev_day(365), only: :weeks) +=> "52 weeks ago" +>> timeago(Date.today.prev_day(365), only: :months) +=> "12 months ago" +>> timeago(Date.today.prev_day(100), only: :weeks) +=> "14 weeks ago" +>> timeago(Date.today.prev_day(500), only: :days) +=> "500 days ago" +``` + +The `only` option can be combined with other options like `style` and `locale`: + +```ruby +>> timeago(Date.today.prev_day(365), only: :weeks, style: :short) +=> "52w ago" +>> timeago(Date.today.prev_day(365), only: :months, locale: :es) +=> "hace 12 meses" +``` + ## Localization By default, `jekyll-timego` already provides translations for some languages. You can check the list [here](lib/locales/). However, you are able to provide your own translations, or even override the originals, easily. @@ -169,6 +193,10 @@ il y a environ 2 années et 6 mois 2y ago > timeago 2016-1-1 2018-1-1 -l es -s short hace 2a y 1d +> timeago 2016-1-1 2018-1-1 --only weeks +104 weeks ago +> timeago 2016-1-1 2018-1-1 --only months -s short +24mo ago ``` ### Console From 3d6c616193a27dec9b98f8d5bbd3d767a0f7466d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:46:16 +0000 Subject: [PATCH 4/6] Update CLI to use OptionParser structure from master and integrate --only option Co-authored-by: markets <576701+markets@users.noreply.github.com> --- bin/timeago | 97 ++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/bin/timeago b/bin/timeago index 559d1e9..f8bf150 100755 --- a/bin/timeago +++ b/bin/timeago @@ -1,70 +1,69 @@ #!/usr/bin/env ruby require_relative "../lib/jekyll-timeago" +require "optparse" -help_message = <<~HELP_MESSAGE - Usage: +options = {} +show_help = false +show_version = false +start_console = false - timeago [to_date] +parser = OptionParser.new do |opts| + opts.banner = <<~HELP_MESSAGE + Usage: - Notes: - [to_date] Optional, defaults to current date + timeago [to_date] - Options: - --help, -h Prints this message - --version, -v Prints the current version - --console, -c Starts an interactive IRB session with jekyll-timeago included - --locale, -l Uses the provided locale - --style, -s Uses the provided style (short) - --only, -o Accumulates time in specified unit (years, months, weeks, days) -HELP_MESSAGE + Notes: + [to_date] Optional, defaults to current date -if ARGV.empty? || ARGV.include?("--help") || ARGV.include?("-h") - puts help_message -elsif ARGV.include?("--version") || ARGV.include?("-v") - puts "v#{Jekyll::Timeago::VERSION}" -elsif ARGV.include?("--console") || ARGV.include?("-c") - require "irb" - include Jekyll::Timeago + Options: + HELP_MESSAGE - ARGV.clear - IRB.start -else - options = {} - - # Handle locale option - custom_locale = "--locale" if ARGV.include?("--locale") - custom_locale = "-l" if ARGV.include?("-l") + opts.on("-h", "--help", "Prints this message") do + show_help = true + end - if custom_locale - index = ARGV.index(custom_locale) - locale = ARGV.delete_at(index + 1) - ARGV.delete_at(index) - options[:locale] = locale + opts.on("-v", "--version", "Prints the current version") do + show_version = true end - # Handle style option - custom_style = "--style" if ARGV.include?("--style") - custom_style = "-s" if ARGV.include?("-s") + opts.on("-c", "--console", "Starts an interactive IRB session with jekyll-timeago included") do + start_console = true + end - if custom_style - index = ARGV.index(custom_style) - style = ARGV.delete_at(index + 1) - ARGV.delete_at(index) - options[:style] = style + opts.on("-l", "--locale LOCALE", "Uses the provided locale") do |locale| + options[:locale] = locale end - # Handle only option - custom_only = "--only" if ARGV.include?("--only") - custom_only = "-o" if ARGV.include?("-o") + opts.on("-s", "--style STYLE", "Uses the provided style (short, array)") do |style| + options[:style] = style + end - if custom_only - index = ARGV.index(custom_only) - only = ARGV.delete_at(index + 1) - ARGV.delete_at(index) - options[:only] = only + opts.on("-o", "--only UNIT", "Accumulates time in specified unit (years, months, weeks, days)") do |unit| + options[:only] = unit end +end + +begin + parser.parse! +rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e + puts "Error! #{e}" + puts parser + exit 1 +end +if show_help + puts parser +elsif show_version + puts "v#{Jekyll::Timeago::VERSION}" +elsif start_console + require "irb" + include Jekyll::Timeago + IRB.start +elsif ARGV.empty? + puts parser +else ARGV << options if !options.empty? begin From d10267c425fe6430f8171262e12affe9db68d11f Mon Sep 17 00:00:00 2001 From: Marc Anguera Insa Date: Mon, 11 Aug 2025 20:56:31 +0200 Subject: [PATCH 5/6] simplify --- README.md | 5 ----- lib/jekyll-timeago/core.rb | 14 ++++---------- spec/jekyll-timeago_spec.rb | 29 ----------------------------- 3 files changed, 4 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 9075a78..625610a 100644 --- a/README.md +++ b/README.md @@ -153,11 +153,6 @@ Use the `only` option to accumulate all time into a single unit. Supported value => "14 weeks ago" >> timeago(Date.today.prev_day(500), only: :days) => "500 days ago" -``` - -The `only` option can be combined with other options like `style` and `locale`: - -```ruby >> timeago(Date.today.prev_day(365), only: :weeks, style: :short) => "52w ago" >> timeago(Date.today.prev_day(365), only: :months, locale: :es) diff --git a/lib/jekyll-timeago/core.rb b/lib/jekyll-timeago/core.rb index ebba68d..c5d392f 100644 --- a/lib/jekyll-timeago/core.rb +++ b/lib/jekyll-timeago/core.rb @@ -66,13 +66,9 @@ def validate_only(only) def time_ago_to_now days_passed = (@to - @from).to_i - # Handle special cases only if "only" option is not specified - # or if days_passed is 0 (today should be "today" regardless of only option) - if !@only || days_passed == 0 - return t(:today) if days_passed == 0 - return t(:yesterday) if days_passed == 1 && !@only - return t(:tomorrow) if days_passed == -1 && !@only - end + return t(:today) if days_passed == 0 + return t(:yesterday) if days_passed == 1 + return t(:tomorrow) if days_passed == -1 past_or_future = @from < @to ? :past : :future slots = build_time_ago_slots(days_passed.abs) @@ -100,9 +96,7 @@ def translate_unit(unit, count) # Builds time ranges with natural unit conversions: ['1 month', '5 days'] def build_time_ago_slots(days_passed) # If "only" option is specified, calculate total time in that unit - if @only - return build_only_slots(days_passed) - end + return build_only_slots(days_passed) if @only # Calculate components with natural unit conversions components = calculate_natural_components(days_passed) diff --git a/spec/jekyll-timeago_spec.rb b/spec/jekyll-timeago_spec.rb index 1b144a4..b493abb 100644 --- a/spec/jekyll-timeago_spec.rb +++ b/spec/jekyll-timeago_spec.rb @@ -154,28 +154,6 @@ expect(timeago(sample_date.prev_day(1000), sample_date, only: :years)).to eq('3 years ago') end - it 'handles special cases with "only" option' do - # Today should always return "today" regardless of only option - expect(timeago(sample_date, sample_date, only: :days)).to eq('today') - expect(timeago(sample_date, sample_date, only: :weeks)).to eq('today') - expect(timeago(sample_date, sample_date, only: :months)).to eq('today') - - # Yesterday and tomorrow should use only option when specified - expect(timeago(sample_date.prev_day, sample_date, only: :days)).to eq('1 day ago') - expect(timeago(sample_date.next_day, sample_date, only: :days)).to eq('in 1 day') - - # Small values should round up to minimum 1 of the specified unit - expect(timeago(sample_date.prev_day(1), sample_date, only: :weeks)).to eq('1 week ago') - expect(timeago(sample_date.prev_day(3), sample_date, only: :weeks)).to eq('1 week ago') - expect(timeago(sample_date.prev_day(7), sample_date, only: :months)).to eq('1 month ago') - end - - it 'allows "only" option with future dates' do - expect(timeago(sample_date.next_day(7), sample_date, only: :weeks)).to eq('in 1 week') - expect(timeago(sample_date.next_day(30), sample_date, only: :months)).to eq('in 1 month') - expect(timeago(sample_date.next_day(365), sample_date, only: :years)).to eq('in 1 year') - end - it 'allows "only" option with different styles' do # Test with short style expect(timeago(sample_date.prev_day(365), sample_date, only: :weeks, style: :short)).to eq('52w ago') @@ -190,13 +168,6 @@ expect(timeago(sample_date.prev_day(30), sample_date, only: :weeks, locale: :es)).to eq('hace 4 semanas') expect(timeago(sample_date.prev_day(365), sample_date, only: :months, locale: :fr)).to eq('il y a environ 12 mois') end - - it 'ignores invalid "only" values' do - # Invalid "only" values should be ignored and behave like normal - expect(timeago(sample_date.prev_day(365), sample_date, only: :invalid)).to eq('1 year ago') - expect(timeago(sample_date.prev_day(365), sample_date, only: 'invalid')).to eq('1 year ago') - expect(timeago(sample_date.prev_day(365), sample_date, only: nil)).to eq('1 year ago') - end end context 'CLI' do From f5236de58bb6753aa12fd070eebc3985ec1f3d29 Mon Sep 17 00:00:00 2001 From: Marc Anguera Insa Date: Mon, 11 Aug 2025 20:57:25 +0200 Subject: [PATCH 6/6] simplify --- spec/jekyll-timeago_spec.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/jekyll-timeago_spec.rb b/spec/jekyll-timeago_spec.rb index b493abb..7ae20f3 100644 --- a/spec/jekyll-timeago_spec.rb +++ b/spec/jekyll-timeago_spec.rb @@ -199,9 +199,6 @@ expect(`bin/timeago 2016-1-1 2018-1-1 -s short`).to match("2y and 1d ago") expect(`bin/timeago 2016-1-1 2018-1-1 --style short`).to match("2y and 1d ago") expect(`bin/timeago 2016-1-1 2016-2-1 -s short`).to match("1mo and 1d ago") - end - - it 'with combined locale and style options' do expect(`bin/timeago 2016-1-1 2018-1-1 -l fr -s short`).to match("il y a environ 2a") expect(`bin/timeago 2016-1-1 2018-1-1 --locale ru --style short`).to match("2г и 1д назад") end @@ -210,9 +207,6 @@ expect(`bin/timeago 2016-1-1 2018-1-1 --only weeks`).to match("104 weeks ago") expect(`bin/timeago 2016-1-1 2018-1-1 -o months`).to match("24 months ago") expect(`bin/timeago 2016-1-1 2016-2-1 --only days`).to match("31 days ago") - end - - it 'with combined options including only' do expect(`bin/timeago 2016-1-1 2018-1-1 -l fr --only months`).to match("il y a environ 24 mois") expect(`bin/timeago 2016-1-1 2018-1-1 --only weeks -s short`).to match("104w ago") end