Skip to content

Instantly share code, notes, and snippets.

@vy-let
Created April 28, 2015 01:34
Show Gist options
  • Save vy-let/013433b23e8733ba478b to your computer and use it in GitHub Desktop.
Save vy-let/013433b23e8733ba478b to your computer and use it in GitHub Desktop.
motd-witty
#!/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
@vy-let
Copy link
Author

vy-let commented Apr 28, 2015

Okay, so it's actually a YAML parser, not a JSON one. Makes for cleaner files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment