From aedddf406c42168854b09a46e3fc6e25155b701a Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 9 Aug 2022 00:50:36 +0200 Subject: [PATCH] add humanize --- _includes/block/stats.html | 2 +- _plugins/LICENSE | 21 +++ _plugins/humanize.rb | 258 +++++++++++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 _plugins/LICENSE create mode 100644 _plugins/humanize.rb diff --git a/_includes/block/stats.html b/_includes/block/stats.html index 14196832..15ffbb44 100644 --- a/_includes/block/stats.html +++ b/_includes/block/stats.html @@ -5,7 +5,7 @@ {% assign stats = stats|default:site.data.general.stats.stats %} {% for item in stats %}
-

{{ item.number }}

+

{{ item.number | intword }}

{{ item.name }}

{% endfor %} diff --git a/_plugins/LICENSE b/_plugins/LICENSE new file mode 100644 index 00000000..c8588ff5 --- /dev/null +++ b/_plugins/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ryan Morrissey + +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. \ No newline at end of file diff --git a/_plugins/humanize.rb b/_plugins/humanize.rb new file mode 100644 index 00000000..349e7e42 --- /dev/null +++ b/_plugins/humanize.rb @@ -0,0 +1,258 @@ +module Jekyll + + module Humanize + ## + # This is a port of the Django app `humanize` which adds a "human touch" + # to data. Given that Jekyll produces static sites, some of the original + # methods do not make logical sense (e.g. naturaltime). + # + # Source code can be viewed here: + # https://github.com/django/django + # + # Copyright (c) Django Software Foundation and individual contributors. + # All rights reserved. + + #################### + # PUBLIC METHODS # + #################### + + def ordinal(value, flag=nil) + ## + # Converts an integer to its ordinal as a string. 1 is '1st', 2 is '2nd', + # 3 is '3rd', etc. Works for any integer. + # + # Usage: + # {{ somenum }} >>> 3 + # {{ somenum | ordinal }} >>> '3rd' + # {{ somenum | ordinal: "super" }} >>> '3rd' + + begin + value = value.to_i + flag.to_s.downcase! + rescue Exception => e + puts "#{e.class} #{e}" + return value + end + + suffix = "" + suffixes = ["th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"] + unless [11, 12, 13].include? value % 100 then + suffix = suffixes[value % 10] + else + suffix = suffixes[0] + end + + unless flag and flag == "super" + return "#{value}%s" % suffix + else + return "#{value}%s" % suffix + end + + end + + def intcomma(value, delimiter=",") + ## + # Converts an integer to a string containing commas every three digits. + # For example, 3000 becomes '3,000' and 45000 becomes '45,000'. + # Optionally supports a delimiter override for commas. + # + # Usage: + # {{ post.content | number_of_words }} >>> 12345 + # {{ post.content | number_of_words | intcomma }} >>> '12,345' + # {{ post.content | number_of_words | intcomma: '.' }} >>> '12.345' + + begin + orig = value.to_s + delimiter = delimiter.to_s + rescue Exception => e + puts "#{e.class} #{e}" + return value + end + + copy = orig.strip + copy = orig.gsub(/^(-?\d+)(\d{3})/, "\\1#{delimiter}\\2") + orig == copy ? copy : intcomma(copy, delimiter) + end + + INTWORD_HELPERS = [ + [6, "million"], + [9, "billion"], + [12, "trillion"], + [15, "quadrillion"], + [18, "quintillion"], + [21, "sextillion"], + [24, "septillion"], + [27, "octillion"], + [30, "nonillion"], + [33, "decillion"], + [100, "googol"], + ] + + def intword(value) + ## + # Converts a large integer to a friendly text representation. Works best + # for numbers over 1 million. For example, 1000000 becomes '1.0 million', + # 1200000 becomes '1.2 million' and 1200000000 becomes '1.2 billion'. + # + # Usage: + # {{ largenum }} >>> 1200000 + # {{ largenum | intword }} >>> '1.2 million' + + begin + value = value.to_i + rescue Exception => e + puts "#{e.class} #{e}" + return value + end + + if value < 1000000 + return value + end + + for exponent, text in INTWORD_HELPERS + large_number = 10 ** exponent + + if value < large_number * 1000 + return "%#{value}.1f #{text}" % (value / large_number.to_f) + end + + end + + return value + end + + def apnumber(value) + ## + # For numbers 0-9, returns the number spelled out. Otherwise, returns the + # number. This follows Associated Press style. + # + # Usage: + # {{ num }} >>> 6 + # {{ num | apnumber }} >>> six + + begin + value = value.to_i + rescue Exception => e + puts "#{e.class} #{e}" + return value + end + + unless value >= 0 and value < 10 then + return value + else + return ["zero", "one", "two", "three", "four", "five", "six", + "seven", "eight", "nine"][value] + end + + end + + def naturalday(date) + ## + # For date values that are within a 9 day stretch from present day, this + # will attempt to return the string representation in the format of today, + # tomorrow, yesterday, "in # days" or "# days ago". Otherwise, returns a + # string formatted according to the "date_format" setting in your + # _config.yml file using strftime format (if not defined, it will default + # to "%m/%d/%Y"). + # + # Usage: + # TODAY == 01/26/2014 + # {{ post.updated }} >>> 01/25/2014 + # {{ post.updated | naturalday }} >>> 'yesterday' + # {{ post.date }} >>> 01/19/2014 + # {{ post.date | naturalday }} >>> 'seven days ago' + + begin + site = @context.registers[:site] + date_format = site.config['humanize']['date_format'] + date = time(date).to_date + rescue Exception => e + puts "#{e.class} #{e}" + return date + end + + unless date_format then + date_format = "%m/%d/%Y" + end + + today = time(Time.now).to_date + delta = (date - today).to_i + + case delta + when 0 + return "today" + when 1 + return "tomorrow" + when 2..9 + delta = apnumber(delta) + return "in #{delta} days" + when -1 + return "yesterday" + when -9..-2 + delta = apnumber(delta * -1) + return "#{delta} days ago" + else + return date.strftime("#{date_format}") + end + + end + + def filesize(value) + ## + # For filesize values in bytes, returns the number rounded to 3 + # decimal places with the correct suffix. + # + # Usage: + # {{ bytes }} >>> 123456789 + # {{ bytes | filesize }} >>> 117.738 MB + filesize_tb = 1099511627776.0 + filesize_gb = 1073741824.0 + filesize_mb = 1048576.0 + filesize_kb = 1024.0 + + begin + value = value.to_f + rescue Exception => e + puts "#{e.class} #{e}" + return value + end + + if value >= filesize_tb + return "%s TB" % (value / filesize_tb).to_f.round(3) + elsif value >= filesize_gb + return "%s GB" % (value / filesize_gb).to_f.round(3) + elsif value >= filesize_mb + return "%s MB" % (value / filesize_mb).to_f.round(3) + elsif value >= filesize_kb + return "%s KB" % (value / filesize_kb).to_f.round(0) + elsif value == 1 + return "1 byte" + else + return "%s bytes" % value.to_f.round(0) + end + + end + + ##################### + # PRIVATE METHODS # + ##################### + + private + def time(input) + case input + when Time + input + when String + Time.parse(input) + else + Jekyll.logger.error "Invalid Date:", "'#{input}' not valid datetime." + exit(1) + end + end + + end + + end + + Liquid::Template.register_filter(Jekyll::Humanize) + \ No newline at end of file