From 4d57d910a4cfeae6f1e5bfea646053b489324ce2 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 19 Dec 2014 22:59:41 -0500 Subject: re-jigger bin/meta-normalize-stdio to be {strict,self-documenting} --- bin/meta-normalize-stdio | 192 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 164 insertions(+), 28 deletions(-) (limited to 'bin') diff --git a/bin/meta-normalize-stdio b/bin/meta-normalize-stdio index 901ca0b..c49dcd9 100755 --- a/bin/meta-normalize-stdio +++ b/bin/meta-normalize-stdio @@ -1,33 +1,169 @@ #!/usr/bin/env ruby + +# First we define a bunch of code-generators, then at the end is a +# very neat and readable definition of the format of the YAML files. + require 'yaml' -core_order = [ "username", - "fullname", - "email", # ordered list - "groups", # ordered list - "pgp_keyid", - "pgp_revoked_keyids", # unordered list - "ssh_keys", # unordered map - "extra" ] # unordered map - -extra_order = [ "alias", - "other_contact", - "roles", - "website", - "occupation", - "yob", - "location", - "languages", - "interests", - "favorite_distros" ] - -_core_order = Hash[[*core_order.map.with_index]] -_extra_order = Hash[[*extra_order.map.with_index]] - -user = YAML::load(STDIN) -user = Hash[user.sort_by{|k,v| _core_order[k]}]} -user["pgp_revoked_keyids"] = user["pgp_revoked_keyids"].sort if user["extra"]} -user["ssh_keys"] = Hash[user["ssh_keys"].sort_by{|k,v| k}] if user["ssh_keys"]} -user["extra"] = Hash[user["extra"].sort_by{|k,v| _extra_order[k]}] if user["extra"]} +def error(msg) + $stderr.puts "ERROR: #{msg}" + @err = 1 +end + +def warning(msg) + $stderr.puts "WARNING: #{msg}" +end + + +# Generic validators/formatters + +def semiordered_list(cnt, validator) + lambda {|name,ary| + if ary.class != Array + error "`#{name}' must be a list" + else + ary.each_index{|i| ary[i] = validator.call("#{name}[#{i}]", ary[i])} + ary = ary.first(cnt).concat(ary.last(ary.count-cnt).sort) + end + ary + } +end + +def unordered_list(validator) + semiordered_list(0, validator) +end + +def _unknown(map_name, key) + error "Unknown item: #{map_name}[#{key.inspect}]" + 0 +end +def unordered_map1(validator) + lambda {|name,hash| + if hash.class != Hash + error "`#{name}' must be a map" + else + order = Hash[[*validator.keys.map.with_index]] + hash = Hash[hash.sort_by{|k,v| order[k] || _unknown(name,k) }] + hash.keys.each{|k| + hash[k] = validator[k].call("#{name}[#{k.inspect}]", hash[k]) + } + end + hash + } +end + +def unordered_map2(key_validator, val_validator) + lambda {|name,hash| + if hash.class != Hash + error "`#{name}' must be a map" + else + hash = Hash[hash.sort_by{|k,v| k}] + hash.keys.each{|k| + key_validator.call("#{name} key #{k.inspect}", k) + hash[k] = val_validator.call("#{name}[#{k.inspect}]", hash[k]) + } + end + hash + } +end + +string = lambda {|name,str| + if str.class != String + error "`#{name}' must be a string" + else + str + end +} + +# Regular Expression String +def restring(re) + lambda {|name,str| + if str.class != String + error "`#{name}' must be a string" + else + unless re =~ str + error "`#{name}' does not match #{re.inspect}: #{str}" + end + str + end + } +end + + +# Specific validators/formatters + +year = lambda {|name, num| + if num.class != Fixnum + error "`#{name}' must be a year" + else + if (num < 1900 || num > 3000) + error "`#{name}' is a number, but doesn't look like a year" + end + num + end +} + +# This regex is taken from http://www.w3.org/TR/html5/forms.html#valid-e-mail-address +_email_regex = /^[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ +email_list = lambda {|name, ary| + if ary.class != Array + error "`#{name}' must be a list" + elsif ary.count > 1 + preserve = 1 + if ary.first.end_with?("@parabola.nu") and ary.count > 2 + preserve = 2 + end + ary = semiordered_list(preserve, restring(_email_regex)).call(name, ary) + end + ary +} + +shell = lambda {|name, sh| + if sh.class != String + error "`#{name}' must be a string" + else + @valid_shells ||= open("/etc/shells").read.split("\n") + .find_all{|line| /^[^\#]/ =~ line} + unless @valid_shells.include?(sh) + warning "shell not listed in /etc/shells: #{sh}" + end + end + sh +} + + +# The format of the YAML files + +format = unordered_map1( + { + "username" => restring(/^[a-z][a-z0-9]*$/), + "fullname" => string, + "email" => email_list, + "groups" => semiordered_list(1, string), + "pgp_keyid" => restring(/^[0-9A-F]{40}$/), + "pgp_revoked_keyids" => unordered_list(restring(/^[0-9A-F]{40}$/)), + "ssh_keys" => unordered_map2(string, string), + "shell" => shell, + "extra" => unordered_map1( + { + "alias" => string, + "other_contact" => string, + "roles" => string, + "website" => string, + "occupation" => string, + "yob" => year, + "location" => string, + "languages" => string, + "interests" => string, + "favorite_distros" => string, + }) + }) + + +@err = 0 +user = format.call("user", YAML::load(STDIN)) +if @err != 0 + exit @err +end print user.to_yaml -- cgit v1.2.3