mirror of
https://github.com/ivuorinen/homebrew-tap.git
synced 2026-03-13 06:00:52 +00:00
refactor: fix Style/OneClassPerFile by extracting top-level definitions into separate files (#18)
This commit is contained in:
9
scripts/array_extensions.rb
Normal file
9
scripts/array_extensions.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Simple polyfill for Homebrew extensions
|
||||||
|
class Array
|
||||||
|
def exclude?(item)
|
||||||
|
!include?(item)
|
||||||
|
end
|
||||||
|
end
|
||||||
77
scripts/asset_processor.rb
Normal file
77
scripts/asset_processor.rb
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "fileutils"
|
||||||
|
require "pathname"
|
||||||
|
require "terser"
|
||||||
|
require "cssminify2"
|
||||||
|
|
||||||
|
# Module for processing and copying assets
|
||||||
|
module AssetProcessor
|
||||||
|
DOCS_DIR = File.expand_path("../docs", __dir__).freeze
|
||||||
|
OUTPUT_DIR = DOCS_DIR
|
||||||
|
THEME_SOURCE_DIR = File.expand_path("../theme", __dir__).freeze
|
||||||
|
|
||||||
|
def copy_assets
|
||||||
|
copy_asset_files
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_css
|
||||||
|
css_path = File.join(THEME_SOURCE_DIR, "style.css")
|
||||||
|
output_path = File.join(OUTPUT_DIR, "style.css")
|
||||||
|
|
||||||
|
return unless File.exist?(css_path)
|
||||||
|
|
||||||
|
css_content = File.read(css_path)
|
||||||
|
minified_css = CSSminify2.compress(css_content)
|
||||||
|
File.write(output_path, minified_css)
|
||||||
|
puts "📄 Generated CSS file: #{output_path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def minify_js
|
||||||
|
js_path = File.join(THEME_SOURCE_DIR, "main.js")
|
||||||
|
output_path = File.join(OUTPUT_DIR, "main.js")
|
||||||
|
|
||||||
|
return unless File.exist?(js_path)
|
||||||
|
|
||||||
|
js_content = File.read(js_path)
|
||||||
|
minified_js = Terser.new.compile(js_content)
|
||||||
|
File.write(output_path, minified_js)
|
||||||
|
puts "🔧 Generated JS file: #{output_path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def copy_asset_files
|
||||||
|
assets_source_dir = File.join(THEME_SOURCE_DIR, "assets")
|
||||||
|
assets_output_dir = File.join(OUTPUT_DIR, "assets")
|
||||||
|
|
||||||
|
FileUtils.mkdir_p(assets_output_dir)
|
||||||
|
|
||||||
|
return handle_missing_assets(assets_source_dir) unless Dir.exist?(assets_source_dir)
|
||||||
|
|
||||||
|
copy_files_recursively(assets_source_dir, assets_output_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_missing_assets(assets_source_dir)
|
||||||
|
puts "⚠️ Assets source directory not found: #{assets_source_dir}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def copy_files_recursively(source_dir, output_dir)
|
||||||
|
asset_files = Dir.glob(File.join(source_dir, "**", "*")).reject { |f| File.directory?(f) }
|
||||||
|
|
||||||
|
asset_files.each do |source_file|
|
||||||
|
copy_single_asset(source_file, source_dir, output_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "📁 Copied #{asset_files.count} asset files to #{output_dir}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def copy_single_asset(source_file, source_dir, output_dir)
|
||||||
|
relative_path = Pathname.new(source_file).relative_path_from(Pathname.new(source_dir))
|
||||||
|
output_file = File.join(output_dir, relative_path)
|
||||||
|
|
||||||
|
FileUtils.mkdir_p(File.dirname(output_file))
|
||||||
|
FileUtils.cp(source_file, output_file)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,146 +9,9 @@ require "pathname"
|
|||||||
require "time"
|
require "time"
|
||||||
require "terser"
|
require "terser"
|
||||||
require "cssminify2"
|
require "cssminify2"
|
||||||
|
require_relative "array_extensions"
|
||||||
# Simple polyfill for Homebrew extensions
|
require_relative "time_formatter"
|
||||||
class Array
|
require_relative "asset_processor"
|
||||||
def exclude?(item)
|
|
||||||
!include?(item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Simple static site generator for homebrew tap documentation
|
|
||||||
# Module for formatting timestamps and dates
|
|
||||||
module TimeFormatter
|
|
||||||
SECONDS_PER_MINUTE = 60
|
|
||||||
SECONDS_PER_HOUR = 3600
|
|
||||||
SECONDS_PER_DAY = 86_400
|
|
||||||
SECONDS_PER_WEEK = 604_800
|
|
||||||
SECONDS_PER_MONTH = 2_419_200
|
|
||||||
SECONDS_PER_YEAR = 31_536_000
|
|
||||||
|
|
||||||
def format_relative_time(timestamp)
|
|
||||||
return "" unless timestamp
|
|
||||||
|
|
||||||
begin
|
|
||||||
diff = calculate_time_difference(timestamp)
|
|
||||||
return "just now" if diff < SECONDS_PER_MINUTE
|
|
||||||
|
|
||||||
format_time_by_category(diff)
|
|
||||||
rescue
|
|
||||||
""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_date(timestamp)
|
|
||||||
return "" unless timestamp
|
|
||||||
|
|
||||||
begin
|
|
||||||
Time.parse(timestamp).strftime("%b %d, %Y")
|
|
||||||
rescue
|
|
||||||
""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def calculate_time_difference(timestamp)
|
|
||||||
time = Time.parse(timestamp)
|
|
||||||
Time.now - time
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_time_by_category(diff)
|
|
||||||
case diff
|
|
||||||
when SECONDS_PER_MINUTE...SECONDS_PER_HOUR
|
|
||||||
format_time_unit(diff / SECONDS_PER_MINUTE, "minute")
|
|
||||||
when SECONDS_PER_HOUR...SECONDS_PER_DAY
|
|
||||||
format_time_unit(diff / SECONDS_PER_HOUR, "hour")
|
|
||||||
when SECONDS_PER_DAY...SECONDS_PER_WEEK
|
|
||||||
format_time_unit(diff / SECONDS_PER_DAY, "day")
|
|
||||||
when SECONDS_PER_WEEK...SECONDS_PER_MONTH
|
|
||||||
format_time_unit(diff / SECONDS_PER_WEEK, "week")
|
|
||||||
when SECONDS_PER_MONTH...SECONDS_PER_YEAR
|
|
||||||
format_time_unit(diff / SECONDS_PER_MONTH, "month")
|
|
||||||
else
|
|
||||||
format_time_unit(diff / SECONDS_PER_YEAR, "year")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_time_unit(value, unit)
|
|
||||||
count = value.to_i
|
|
||||||
"#{count} #{unit}#{"s" if count != 1} ago"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Module for processing and copying assets
|
|
||||||
module AssetProcessor
|
|
||||||
DOCS_DIR = File.expand_path("../docs", __dir__).freeze
|
|
||||||
OUTPUT_DIR = DOCS_DIR
|
|
||||||
THEME_SOURCE_DIR = File.expand_path("../theme", __dir__).freeze
|
|
||||||
|
|
||||||
def copy_assets
|
|
||||||
copy_asset_files
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_css
|
|
||||||
css_path = File.join(THEME_SOURCE_DIR, "style.css")
|
|
||||||
output_path = File.join(OUTPUT_DIR, "style.css")
|
|
||||||
|
|
||||||
return unless File.exist?(css_path)
|
|
||||||
|
|
||||||
css_content = File.read(css_path)
|
|
||||||
minified_css = CSSminify2.compress(css_content)
|
|
||||||
File.write(output_path, minified_css)
|
|
||||||
puts "📄 Generated CSS file: #{output_path}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def minify_js
|
|
||||||
js_path = File.join(THEME_SOURCE_DIR, "main.js")
|
|
||||||
output_path = File.join(OUTPUT_DIR, "main.js")
|
|
||||||
|
|
||||||
return unless File.exist?(js_path)
|
|
||||||
|
|
||||||
js_content = File.read(js_path)
|
|
||||||
minified_js = Terser.new.compile(js_content)
|
|
||||||
File.write(output_path, minified_js)
|
|
||||||
puts "🔧 Generated JS file: #{output_path}"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def copy_asset_files
|
|
||||||
assets_source_dir = File.join(THEME_SOURCE_DIR, "assets")
|
|
||||||
assets_output_dir = File.join(OUTPUT_DIR, "assets")
|
|
||||||
|
|
||||||
FileUtils.mkdir_p(assets_output_dir)
|
|
||||||
|
|
||||||
return handle_missing_assets(assets_source_dir) unless Dir.exist?(assets_source_dir)
|
|
||||||
|
|
||||||
copy_files_recursively(assets_source_dir, assets_output_dir)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_missing_assets(assets_source_dir)
|
|
||||||
puts "⚠️ Assets source directory not found: #{assets_source_dir}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def copy_files_recursively(source_dir, output_dir)
|
|
||||||
asset_files = Dir.glob(File.join(source_dir, "**", "*")).reject { |f| File.directory?(f) }
|
|
||||||
|
|
||||||
asset_files.each do |source_file|
|
|
||||||
copy_single_asset(source_file, source_dir, output_dir)
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "📁 Copied #{asset_files.count} asset files to #{output_dir}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def copy_single_asset(source_file, source_dir, output_dir)
|
|
||||||
relative_path = Pathname.new(source_file).relative_path_from(Pathname.new(source_dir))
|
|
||||||
output_file = File.join(output_dir, relative_path)
|
|
||||||
|
|
||||||
FileUtils.mkdir_p(File.dirname(output_file))
|
|
||||||
FileUtils.cp(source_file, output_file)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Static site generator for homebrew tap documentation
|
# Static site generator for homebrew tap documentation
|
||||||
class SiteBuilder
|
class SiteBuilder
|
||||||
|
|||||||
123
scripts/file_watcher.rb
Normal file
123
scripts/file_watcher.rb
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Module for handling file watching and change detection.
|
||||||
|
# Classes including this module must define a `build_site` method.
|
||||||
|
module FileWatcher
|
||||||
|
def start_file_watcher
|
||||||
|
Thread.new do
|
||||||
|
last_mtime = max_mtime
|
||||||
|
rebuild_pending = false
|
||||||
|
watched_files = watched_files_count
|
||||||
|
|
||||||
|
puts "👀 Watching #{watched_files} files for changes..."
|
||||||
|
|
||||||
|
loop do
|
||||||
|
sleep 1
|
||||||
|
current_mtime = max_mtime
|
||||||
|
|
||||||
|
next if should_skip_rebuild?(current_mtime, last_mtime, rebuild_pending)
|
||||||
|
|
||||||
|
rebuild_pending = true
|
||||||
|
handle_file_change(last_mtime)
|
||||||
|
last_mtime = perform_rebuild_with_debounce
|
||||||
|
rebuild_pending = false
|
||||||
|
puts "✅ Rebuild complete"
|
||||||
|
rescue => e
|
||||||
|
puts "⚠️ File watcher error: #{e.message}"
|
||||||
|
puts "📍 Backtrace: #{e.backtrace.first(3).join(", ")}"
|
||||||
|
rebuild_pending = false
|
||||||
|
sleep 2
|
||||||
|
puts "🔄 File watcher continuing..."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def watched_files_count
|
||||||
|
all_watched_files.count { |file| !File.directory?(file) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_changed_file(since_mtime)
|
||||||
|
all_watched_files.find do |file|
|
||||||
|
File.exist?(file) && !File.directory?(file) && File.mtime(file) > since_mtime
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def all_watched_files
|
||||||
|
[
|
||||||
|
formula_files,
|
||||||
|
theme_files,
|
||||||
|
template_files,
|
||||||
|
style_and_script_files,
|
||||||
|
asset_files,
|
||||||
|
build_script_files,
|
||||||
|
config_files,
|
||||||
|
].flatten.compact.uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def max_mtime
|
||||||
|
all_watched_files
|
||||||
|
.select { |file| File.exist?(file) && !File.directory?(file) }
|
||||||
|
.map { |file| File.mtime(file) }
|
||||||
|
.max || Time.at(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def should_skip_rebuild?(current_mtime, last_mtime, rebuild_pending)
|
||||||
|
current_mtime <= last_mtime || rebuild_pending
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_file_change(last_mtime)
|
||||||
|
changed_file = find_changed_file(last_mtime)
|
||||||
|
puts "📝 Changed: #{changed_file}" if changed_file
|
||||||
|
puts "🔄 Rebuilding in 1 second..."
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform_rebuild_with_debounce
|
||||||
|
sleep 1 # Debounce: wait for additional changes
|
||||||
|
final_mtime = max_mtime
|
||||||
|
puts "🔨 Building site..."
|
||||||
|
build_site
|
||||||
|
final_mtime
|
||||||
|
end
|
||||||
|
|
||||||
|
def formula_files
|
||||||
|
Dir.glob(File.expand_path("../Formula/**/*.rb", __dir__))
|
||||||
|
end
|
||||||
|
|
||||||
|
def theme_files
|
||||||
|
Dir.glob(File.expand_path("../theme/**/*", __dir__))
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_files
|
||||||
|
[
|
||||||
|
Dir.glob(File.expand_path("../theme/*.erb", __dir__)),
|
||||||
|
Dir.glob(File.expand_path("../theme/_*.erb", __dir__)),
|
||||||
|
Dir.glob(File.expand_path("../theme/*.html.erb", __dir__)),
|
||||||
|
Dir.glob(File.expand_path("../theme/_*.html.erb", __dir__)),
|
||||||
|
].flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
def style_and_script_files
|
||||||
|
[
|
||||||
|
Dir.glob(File.expand_path("../theme/*.css", __dir__)),
|
||||||
|
Dir.glob(File.expand_path("../theme/*.js", __dir__)),
|
||||||
|
].flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
def asset_files
|
||||||
|
Dir.glob(File.expand_path("../theme/assets/**/*", __dir__))
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_script_files
|
||||||
|
[
|
||||||
|
File.expand_path("../scripts/parse_formulas.rb", __dir__),
|
||||||
|
File.expand_path("../scripts/build_site.rb", __dir__),
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def config_files
|
||||||
|
[File.expand_path("../Makefile", __dir__)]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,14 +6,7 @@ require "json"
|
|||||||
require "fileutils"
|
require "fileutils"
|
||||||
require "pathname"
|
require "pathname"
|
||||||
require "date"
|
require "date"
|
||||||
|
require_relative "string_extensions"
|
||||||
# Simple polyfill for Homebrew extensions
|
|
||||||
class String
|
|
||||||
def blank?
|
|
||||||
# Polyfill implementation to avoid external dependencies
|
|
||||||
nil? || empty? # rubocop:disable Homebrew/Blank, Lint/RedundantCopDisableDirective
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parser class for extracting metadata from Homebrew formulae
|
# Parser class for extracting metadata from Homebrew formulae
|
||||||
class FormulaParser
|
class FormulaParser
|
||||||
|
|||||||
122
scripts/serve.rb
122
scripts/serve.rb
@@ -6,127 +6,7 @@ require "webrick"
|
|||||||
require "fileutils"
|
require "fileutils"
|
||||||
require_relative "parse_formulas"
|
require_relative "parse_formulas"
|
||||||
require_relative "build_site"
|
require_relative "build_site"
|
||||||
|
require_relative "file_watcher"
|
||||||
# Simple development server for the homebrew tap documentation
|
|
||||||
# Module for handling file watching and change detection
|
|
||||||
module FileWatcher
|
|
||||||
def start_file_watcher
|
|
||||||
Thread.new do
|
|
||||||
last_mtime = max_mtime
|
|
||||||
rebuild_pending = false
|
|
||||||
watched_files = watched_files_count
|
|
||||||
|
|
||||||
puts "👀 Watching #{watched_files} files for changes..."
|
|
||||||
|
|
||||||
loop do
|
|
||||||
sleep 1
|
|
||||||
current_mtime = max_mtime
|
|
||||||
|
|
||||||
next if should_skip_rebuild?(current_mtime, last_mtime, rebuild_pending)
|
|
||||||
|
|
||||||
rebuild_pending = true
|
|
||||||
handle_file_change(last_mtime)
|
|
||||||
last_mtime = perform_rebuild_with_debounce
|
|
||||||
rebuild_pending = false
|
|
||||||
puts "✅ Rebuild complete"
|
|
||||||
rescue => e
|
|
||||||
puts "⚠️ File watcher error: #{e.message}"
|
|
||||||
puts "📍 Backtrace: #{e.backtrace.first(3).join(", ")}"
|
|
||||||
rebuild_pending = false
|
|
||||||
sleep 2
|
|
||||||
puts "🔄 File watcher continuing..."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def watched_files_count
|
|
||||||
all_watched_files.count { |file| !File.directory?(file) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_changed_file(since_mtime)
|
|
||||||
all_watched_files.find do |file|
|
|
||||||
File.exist?(file) && !File.directory?(file) && File.mtime(file) > since_mtime
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def all_watched_files
|
|
||||||
[
|
|
||||||
formula_files,
|
|
||||||
theme_files,
|
|
||||||
template_files,
|
|
||||||
style_and_script_files,
|
|
||||||
asset_files,
|
|
||||||
build_script_files,
|
|
||||||
config_files,
|
|
||||||
].flatten.compact.uniq
|
|
||||||
end
|
|
||||||
|
|
||||||
def max_mtime
|
|
||||||
all_watched_files
|
|
||||||
.select { |file| File.exist?(file) && !File.directory?(file) }
|
|
||||||
.map { |file| File.mtime(file) }
|
|
||||||
.max || Time.at(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def should_skip_rebuild?(current_mtime, last_mtime, rebuild_pending)
|
|
||||||
current_mtime <= last_mtime || rebuild_pending
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_file_change(last_mtime)
|
|
||||||
changed_file = find_changed_file(last_mtime)
|
|
||||||
puts "📝 Changed: #{changed_file}" if changed_file
|
|
||||||
puts "🔄 Rebuilding in 1 second..."
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_rebuild_with_debounce
|
|
||||||
sleep 1 # Debounce: wait for additional changes
|
|
||||||
final_mtime = max_mtime
|
|
||||||
puts "🔨 Building site..."
|
|
||||||
build_site
|
|
||||||
final_mtime
|
|
||||||
end
|
|
||||||
|
|
||||||
def formula_files
|
|
||||||
Dir.glob(File.expand_path("../Formula/**/*.rb", __dir__))
|
|
||||||
end
|
|
||||||
|
|
||||||
def theme_files
|
|
||||||
Dir.glob(File.expand_path("../theme/**/*", __dir__))
|
|
||||||
end
|
|
||||||
|
|
||||||
def template_files
|
|
||||||
[
|
|
||||||
Dir.glob(File.expand_path("../theme/*.erb", __dir__)),
|
|
||||||
Dir.glob(File.expand_path("../theme/_*.erb", __dir__)),
|
|
||||||
Dir.glob(File.expand_path("../theme/*.html.erb", __dir__)),
|
|
||||||
Dir.glob(File.expand_path("../theme/_*.html.erb", __dir__)),
|
|
||||||
].flatten
|
|
||||||
end
|
|
||||||
|
|
||||||
def style_and_script_files
|
|
||||||
[
|
|
||||||
Dir.glob(File.expand_path("../theme/*.css", __dir__)),
|
|
||||||
Dir.glob(File.expand_path("../theme/*.js", __dir__)),
|
|
||||||
].flatten
|
|
||||||
end
|
|
||||||
|
|
||||||
def asset_files
|
|
||||||
Dir.glob(File.expand_path("../theme/assets/**/*", __dir__))
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_script_files
|
|
||||||
[
|
|
||||||
File.expand_path("../scripts/parse_formulas.rb", __dir__),
|
|
||||||
File.expand_path("../scripts/build_site.rb", __dir__),
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
def config_files
|
|
||||||
[File.expand_path("../Makefile", __dir__)]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Development server with file watching for homebrew tap documentation
|
# Development server with file watching for homebrew tap documentation
|
||||||
class DevServer
|
class DevServer
|
||||||
|
|||||||
10
scripts/string_extensions.rb
Normal file
10
scripts/string_extensions.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Simple polyfill for Homebrew extensions
|
||||||
|
class String
|
||||||
|
def blank?
|
||||||
|
# Polyfill implementation to avoid external dependencies
|
||||||
|
nil? || empty? # rubocop:disable Homebrew/Blank, Lint/RedundantCopDisableDirective
|
||||||
|
end
|
||||||
|
end
|
||||||
66
scripts/time_formatter.rb
Normal file
66
scripts/time_formatter.rb
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "time"
|
||||||
|
|
||||||
|
# Module for formatting timestamps and dates
|
||||||
|
module TimeFormatter
|
||||||
|
SECONDS_PER_MINUTE = 60
|
||||||
|
SECONDS_PER_HOUR = 3600
|
||||||
|
SECONDS_PER_DAY = 86_400
|
||||||
|
SECONDS_PER_WEEK = 604_800
|
||||||
|
SECONDS_PER_MONTH = 2_419_200
|
||||||
|
SECONDS_PER_YEAR = 31_536_000
|
||||||
|
|
||||||
|
def format_relative_time(timestamp)
|
||||||
|
return "" unless timestamp
|
||||||
|
|
||||||
|
begin
|
||||||
|
diff = calculate_time_difference(timestamp)
|
||||||
|
return "just now" if diff < SECONDS_PER_MINUTE
|
||||||
|
|
||||||
|
format_time_by_category(diff)
|
||||||
|
rescue
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_date(timestamp)
|
||||||
|
return "" unless timestamp
|
||||||
|
|
||||||
|
begin
|
||||||
|
Time.parse(timestamp).strftime("%b %d, %Y")
|
||||||
|
rescue
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def calculate_time_difference(timestamp)
|
||||||
|
time = Time.parse(timestamp)
|
||||||
|
Time.now - time
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_time_by_category(diff)
|
||||||
|
case diff
|
||||||
|
when SECONDS_PER_MINUTE...SECONDS_PER_HOUR
|
||||||
|
format_time_unit(diff / SECONDS_PER_MINUTE, "minute")
|
||||||
|
when SECONDS_PER_HOUR...SECONDS_PER_DAY
|
||||||
|
format_time_unit(diff / SECONDS_PER_HOUR, "hour")
|
||||||
|
when SECONDS_PER_DAY...SECONDS_PER_WEEK
|
||||||
|
format_time_unit(diff / SECONDS_PER_DAY, "day")
|
||||||
|
when SECONDS_PER_WEEK...SECONDS_PER_MONTH
|
||||||
|
format_time_unit(diff / SECONDS_PER_WEEK, "week")
|
||||||
|
when SECONDS_PER_MONTH...SECONDS_PER_YEAR
|
||||||
|
format_time_unit(diff / SECONDS_PER_MONTH, "month")
|
||||||
|
else
|
||||||
|
format_time_unit(diff / SECONDS_PER_YEAR, "year")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_time_unit(value, unit)
|
||||||
|
count = value.to_i
|
||||||
|
"#{count} #{unit}#{"s" if count != 1} ago"
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user