diff options
Diffstat (limited to 'bin/meta-normalize-stdio')
-rwxr-xr-x | bin/meta-normalize-stdio | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/bin/meta-normalize-stdio b/bin/meta-normalize-stdio new file mode 100755 index 0000000..a7ca381 --- /dev/null +++ b/bin/meta-normalize-stdio @@ -0,0 +1,190 @@ +#!/usr/bin/env ruby +# coding: utf-8 + +# Copyright 2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>. +# Copyright 2015 Márcio Alexandre Silva Delgado <coadde@parabola.nu>. +# +# This is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this manual; if not, see +# <http://www.gnu.org/licenses/>. + +# 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' + +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| + if validator[k] + hash[k] = validator[k].call("#{name}[#{k.inspect}]", hash[k]) + end + } + 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 not ary.empty? + 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} + .push("/usr/bin/nologin") + 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 |