Created
April 28, 2015 01:34
-
-
Save vy-let/013433b23e8733ba478b to your computer and use it in GitHub Desktop.
motd-witty
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
GREETINGS_FILE = '/etc/motd-witty.json' | |
# | |
# motd-witty | |
# | |
# Version 1.1 | |
# Talus Baddley, 2013. | |
# Copyright; GNU GPL v2. | |
# | |
# | |
# (I really should come up with a better name for this.) | |
# | |
# motd-witty provides a simple mechanism for both important | |
# announcements and (as the name implies) witty quips and pro-tips | |
# randomly selected for each user. The former we call Messages and | |
# the latter we simply refer to as Protips. Additionally, any of | |
# these may be limited to a particular time-frame. | |
# | |
# This script is intended to work as a part of Ubuntu's update-motd.d | |
# system. It reads greetings the administrator places in the | |
# GREETINGS_FILE as specified above, and writes them out to the | |
# message of the day as any user logs in. (Read man update-motd for | |
# more info on the larger system in which this operates.) | |
# | |
# The format for the greetings file is a JSON array of records. | |
# A 'record' is a dictionary defining a date range, and either | |
# a message or a set of protips. | |
# | |
# An example file: | |
# [ | |
# { | |
# "startDate": "2013-09-20T17:18:05-0600", | |
# "lifetime": 1.75, | |
# "message": "Updates are done for this week.\nYou may safely carry on.\n\n" | |
# }, | |
# { | |
# "startDate": "2013-09-23T06:00:00-0600", | |
# "lifetime": 7, | |
# "protips": [ | |
# "Because three heads are better than four cores.", | |
# "This week's shell brought to you by Jason Bourne." | |
# ] | |
# }, | |
# { | |
# "default": true, | |
# "protips": [ | |
# "Protip: run `cowsay Moo!` periodically.", | |
# "Don't try and sudo. The terrier reviews all such incidents." | |
# ] | |
# } | |
# ] | |
# | |
# We see the outer array [] and its dictionary records {}, {}, {} | |
# In a record, we see a message string or a protips array of string. | |
# The record's startDate should be expressed according to ISO 8601. | |
# That is, yyyy-mm-ddThh:mm:ss-timezone. | |
# The lifetime of a record after its startDate is expressed in days. | |
# Note that Salt Lake is in timezone -0600 in daylight savings and | |
# -0700 otherwise. | |
# | |
# At the moment a user logs in, all messages applicable to that | |
# moment (according to their date ranges) are collected and displayed | |
# to the user, in the order they appear in the file. If there are no | |
# applicable messages, all the applicable protips are pooled, and one | |
# of them is randomly shown to the user. | |
# | |
# We also see the special default record at the end. | |
# Its contents are always considered applicable. | |
# | |
# Protip: for a message to be seen, end it in \n\n | |
# as in the example. | |
# Update for 1.1: Now uses JSON rather than XML Property List, | |
# as Ruby's plist interpreter is nonconformant. | |
require 'time' | |
require 'yaml' | |
RIGHT_NOW = Time.now | |
TERM_W = 69 # Assume a 70-char terminal. | |
SECONDS_IN_DAY = 86400 # = 24 * 60 * 60 | |
START_DATE_KEY = 'startDate' | |
LIFETIME_KEY = 'lifetime' | |
MAND_MESSAGE_KEY = 'message' | |
OPT_MESSAGES_KEY = 'protips' | |
DEFAULT_KEY = 'default' | |
def do_greet | |
# | |
# 1. | |
# Start by reading in the greetings data. | |
# | |
greetingInfo = extract_greeting_info GREETINGS_FILE | |
return unless greetingInfo | |
# | |
# 2. | |
# Filter out the records which are inapplicable to the current date. | |
# | |
relevantRecords = greetingInfo.select do |record| | |
startDate = record[START_DATE_KEY] | |
lifetime = record[LIFETIME_KEY] | |
next true if startDate.nil? || lifetime.nil? | |
relevant_date?(startDate, lifetime) ? true : false | |
end | |
# | |
# 3. | |
# We would grab the default record, but the 'default' key is | |
# really just commenting: Its not having a date range | |
# automatically includes it in the output. | |
# If there are no applicable records at all, return having done nothing. | |
# | |
return if relevantRecords.empty? | |
# | |
# 4. | |
# Puts the mandatory messages. | |
# | |
mandatoryMessages = relevantRecords.lazymap_nonnil {|record| record[MAND_MESSAGE_KEY] } | |
mandatoryMessages.each {|msg| puts msg } | |
return if mandatoryMessages.first # If there were any messages, exit now. | |
# | |
# 5. | |
# Puts the random protip! | |
# | |
protipBucket = relevantRecords.flat_map do |record| | |
msgs = record[OPT_MESSAGES_KEY] | |
msgs ? msgs : [] # Yields the list of protips if they exist, | |
# otherwise the empty list. | |
end | |
puts protipBucket.sample.center_each_line(TERM_W) unless protipBucket.empty? | |
end | |
class String | |
def center_each_line width | |
self.each_line.map {|line| line.chomp.center width }.join($/) | |
end | |
end | |
def extract_greeting_info inFile | |
resultat = nil | |
File.open(inFile) do |fpipe| | |
resultat = YAML.load( fpipe.set_encoding('UTF-8').read ) | |
end | |
return resultat | |
rescue StandardError | |
return nil | |
end | |
def relevant_date? dateString, lifetime | |
date = Time.iso8601(dateString) rescue Time.parse(dateString) | |
endDate = date + (lifetime * SECONDS_IN_DAY) | |
date <= RIGHT_NOW && endDate > RIGHT_NOW ? true : false | |
end | |
# lazymap_nonnil for Enumerables by Talus Baddley | |
module Enumerable | |
# Maps all values on the receiver through the provided block, | |
# omitting nil values on output. | |
def lazymap_nonnil | |
Enumerator.new do |yielder| | |
self.each do |enumeritem| | |
resultat = yield enumeritem | |
yielder << resultat unless resultat.nil? | |
end | |
end | |
end | |
end | |
do_greet |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Okay, so it's actually a YAML parser, not a JSON one. Makes for cleaner files.