funip/parse.rb

391 lines
13 KiB
Ruby

require "ipaddr"
require "psych"
require "logger"
class String
def blank?
return self.strip.empty?
end
end
$logger = Logger.new $stdout
$logger.level = Logger::INFO
# hardenize logger
formatter = Logger::Formatter.new
$logger.formatter = proc { |severity, datetime, progname, msg|
formatter.call(severity, datetime, progname, msg.dump[1...-1])
}
class Interpreter
attr_accessor :recurLimit
def self.loadFile filename = "script.json"
$logger.info "Load script from file #{filename}."
raise "The script file #{filename} does not exist." if ! File.exists? filename
$logger.debug "File exists."
raise "The script file #{filename} is not readable." if ! File.readable? filename
$logger.debug "File readable."
$logger.info "Read file."
fileContent = File.read filename
$logger.info "Parse script."
code = Psych.safe_load fileContent
raise "An associative array is needed to process the script." if code.is_a? Array
raise "The script file does not contain valid JSON/YAML." if ! code.is_a? Hash
return code
end
def initialize code, stream = $stdout
@code = code
@parsed = false
@stream = stream
@recur = 0
@recurLimit = 128
end
def parse!
$logger.warn "The script has already been parsed." if @parsed
@variables = {}
parseBlock @code
raise "Internal error. A recursion was not completed." if @recur != 0
@parsed = true
end
def isParsed?
return @parsed
end
private
def generatePrefix(lengths, master, exclude)
available_prefixes = []
lengths.each do |len|
# Generiere alle möglichen Präfixe der gegebenen Länge im Master-Präfix
master.mask(len).to_range.each do |p|
# Füge das Präfix nur hinzu, wenn es nicht in exclude enthalten ist
available_prefixes << p.to_s #unless exclude.include?(p.to_s)
end
end
# Wähle ein zufälliges Präfix aus den verbleibenden Präfixen aus
return available_prefixes.sample
end
def generate args
raise "No prefix lengths given." if ! args.has_key? "lengths"
lengths = getValue args["lengths"]
raise "The prefix lengths are not an array." if ! lengths.is_a? Array
raise "No length was specified." if lengths.empty?
lengths.each do |len|
raise "The prefix length is not valid." if len < 1 || len > 128
end
raise "The master prefix was not specified." if ! args.has_key? "in"
master = getValue args["in"]
raise "The master prefix is not from the type prefix." if ! master.is_a? IPAddr
count = 1
if args.has_key? "count"
count = getValue args["count"]
raise "Count is not an integer" if ! count.is_a? Integer
raise "The number must be greater than zero." if count == 0
end
exclude = []
if args.has_key? "exclude"
exclude = getValue args["exclude"]
raise "The list of prefixes to exclude is not an array." if ! exclude.is_a? Array
exclude.each do |prefix|
raise "An item of the array, the list of prefixes to be excluded, is not a prefix." if ! prefix.is_a? IPAddr
end
end
pp generatePrefix(lengths, master, exclude)
end
def parseVariable var, optionalName = false
@recur += 1
if @recur > @recurLimit
raise "Recursion limit of #{@recurLimit} reached."
end
$logger.debug "Parse variable."
raise "The variable must be of type assosicated array." if ! var.is_a? Hash
name = nil
if ! optionalName
raise "Missing variable name." if ! var.has_key? "name"
raise "Variable name of wrong type." if ! var["name"].is_a? String
raise "Empty variable names are not allowed." if var["name"].blank?
name = var["name"]
$logger.debug "Variable name: #{name}"
else
$logger.debug "Variable has no name."
end
raise "Missing variable type." if ! var.has_key? "type"
raise "Variable type of wrong type." if ! var["type"].is_a? String
raise "Empty variable types are not allowed." if var["type"].blank?
type = var["type"]
$logger.debug "Variable type: #{type}"
res = nil
type, subtype = type.split("/")
case type
when "prefix"
raise "Miss CIDR of variable." if ! var.has_key? "cidr"
raise "CIDR content is of the wrong type. Expect type string." if ! var["cidr"].is_a? String
raise "Unknown subtype." if subtype
begin
res = IPAddr.new(var["cidr"])
rescue IPAddr::InvalidAddressError
raise "Invalid prefix."
end
raise "IPv4 is obsolete and is not supported." if res.ipv4?
when "prefixLength"
raise "Miss value of variable." if ! var.has_key? "value"
raise "prefixLength content is of the wrong type. Expect type integer." if ! var["value"].is_a? Integer
raise "Prefix length must be greater than zero." if var["value"] < 0
raise "Prefix length must not be greater than 128." if var["value"] > 128
raise "Unknown subtype." if subtype
res = var["value"]
when "array"
res = []
raise "Miss value of variable." if ! var.has_key? "value"
raise "Array content is of the wrong type. Expect type array." if ! var["value"].is_a? Array
subtypeMapping = {
"string" => "value",
"prefixLength" => "value",
"prefix" => "cidr"
}
raise "Subtype is not supported." if subtype && (! subtypeMapping.keys.include?(subtype))
$logger.debug "Iterate through array."
var["value"].each do |arrVar|
if subtype
newVar = {
"type" => subtype,
subtypeMapping[subtype] => arrVar
}
res << parseVariable(newVar, true)[1]
else
res << parseVariable(arrVar, true)[1]
end
end
when "string"
raise "Miss value of variable." if ! var.has_key? "value"
raise "The value of the type String must be a string." if ! var["value"].is_a? String
raise "Unknown subtype." if subtype
res = var["value"]
else
raise "Unknown variable type."
end
@recur -= 1
return [name, res]
end
def getValue var
if var.is_a? String
case var[0]
when "$"
varname = var[1..]
raise "The variable #{varname} could not be found." if ! @variables.has_key? varname
return @variables[varname]
when "%"
return var[1..]
else
raise "Unknown instruction to read the value."
end
elsif var.is_a? Hash
return parseVariable(var, true)[1]
elsif var.is_a? Integer
return var
else
raise "Unknown instruction to read the value."
end
end
def prettyPrintValue prefix, postfix, val
# do a bit pretty print for known types
if val.is_a? IPAddr
@stream.print "#{prefix}"
@stream.print "#{val.to_s}/#{val.prefix}"
@stream.puts "#{postfix}"
elsif val.is_a? Array
val.each do |entry|
prettyPrintValue prefix, postfix, entry
end
else
@stream.print "#{prefix}"
@stream.print val.to_s
@stream.puts "#{postfix}"
end
end
def prettyPrint args
raise "Value to be printed not specified." if ! args.has_key? "value"
raise "The value must be a string." if ! args["value"].is_a? String
var = getValue args["value"]
prefix = nil
if args.has_key? "prefix"
raise "The prefix must be a string." if ! args["prefix"].is_a? String
prefix = getValue args["prefix"]
raise "The prefix variable must be a string." if ! prefix.is_a? String
end
postfix = nil
if args.has_key? "postfix"
raise "The postfix must be a string." if ! args["postfix"].is_a? String
postfix = getValue args["postfix"]
raise "The postfix variable must be a string." if ! postfix.is_a? String
end
prettyPrintValue prefix, postfix, var
end
def includeFile args
raise "File to be included not specified." if ! args.has_key? "file"
filename = getValue args["file"]
raise "The filename must be a string." if ! filename.is_a? String
to_catch = true
if args.has_key? "catch"
raise "The catch parameter must be a boolean." if ! (args["catch"].is_a?(TrueClass) || args["catch"].is_a?(FalseClass))
to_catch = args["catch"]
end
$logger.info "Include the file #{filename}."
begin
instructions = Interpreter.loadFile filename
parseBlock instructions
rescue RuntimeError => e
puts "An error occurred while including the file #{filename}: #{e.message}"
raise e if ! to_catch
end
end
def forLoop args
$logger.debug "In for loop."
raise "The array to be iterated must be specified." if ! args.has_key? "through"
array = getValue args["through"]
raise "The array to be interacted by is not of type array." if ! array.is_a? Array
raise "The variable name must be specified." if ! args.has_key? "as"
raise "The variable name must be specified as a string." if ! args["as"].is_a? String
raise "The variable name must not be empty." if args["as"].blank?
varName = args["as"]
raise "No instructions were given." if ! args.has_key? "do"
raise "The instructions must be stored as an array." if ! args["do"].is_a? Array
array.each do |value|
@variables[varName] = value
args["do"].each do |action|
raise "An action consists of a name and arguments. Invalid input detected." if action.length < 1 || action.length > 2
name = action[0]
raise "The action name must be a string." if ! name.is_a? String
raise "The action name must not be nothing." if name.blank?
args = {}
if action.length == 1
$logger.debug "No arguments were passed to the action #{name}."
else
raise "The arguments must be of the type assosicated array." if ! action[1].is_a? Hash
args = action[1]
end
performAction name, args
end
end
end
def performAction action, args
@recur += 1
if @recur > @recurLimit
raise "Recursion limit of #{@recurLimit} reached."
end
$logger.debug "Perform action."
case action
when "generate"
generate args
when "for"
forLoop args
when "include"
includeFile args
when "print"
prettyPrint args
else
raise "Unknown action."
end
@recur -= 1
end
def parseBlock block
$logger.info "Parse variables."
if block.has_key? "variables"
raise "The variables were not specified as an array." if ! block["variables"].is_a? Array
$logger.debug "The variables are correctly present as a type array."
block["variables"].each do |var|
name, value = parseVariable var
@variables[name] = value
end
else
$logger.debug "The script does not contain any variables."
end
if block.has_key? "actions"
$logger.debug "Perform actions."
raise "The actions were not specified as an array." if ! block["actions"].is_a? Array
$logger.debug "The actions are correctly present as a type array."
block["actions"].each do |action|
raise "An action consists of a name and arguments. Invalid input detected." if action.length < 1 || action.length > 2
name = action[0]
raise "The action name must be a string." if ! name.is_a? String
raise "The action name must not be nothing." if name.blank?
args = {}
if action.length == 1
$logger.debug "No arguments were passed to the action #{name}."
else
raise "The arguments must be of the type assosicated array." if ! action[1].is_a? Hash
args = action[1]
end
performAction name, args
end
else
$logger.debug "The script does not contain any actions."
end
end
end
$logger.info "Load code."
code = Interpreter.loadFile
$logger.debug "Create interpreter."
i = Interpreter.new code
$logger.debug "Give instruction to parse."
i.parse!