parse.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. require "ipaddr"
  2. require "psych"
  3. require "logger"
  4. class String
  5. def blank?
  6. return self.strip.empty?
  7. end
  8. end
  9. $logger = Logger.new $stdout
  10. $logger.level = Logger::INFO
  11. # hardenize logger
  12. formatter = Logger::Formatter.new
  13. $logger.formatter = proc { |severity, datetime, progname, msg|
  14. formatter.call(severity, datetime, progname, msg.dump[1...-1])
  15. }
  16. class Interpreter
  17. attr_accessor :recurLimit
  18. def self.loadFile filename = "script.json"
  19. $logger.info "Load script from file #{filename}."
  20. raise "The script file #{filename} does not exist." if ! File.exists? filename
  21. $logger.debug "File exists."
  22. raise "The script file #{filename} is not readable." if ! File.readable? filename
  23. $logger.debug "File readable."
  24. $logger.info "Read file."
  25. fileContent = File.read filename
  26. $logger.info "Parse script."
  27. code = Psych.safe_load fileContent
  28. raise "An associative array is needed to process the script." if code.is_a? Array
  29. raise "The script file does not contain valid JSON/YAML." if ! code.is_a? Hash
  30. return code
  31. end
  32. def initialize code, stream = $stdout
  33. @code = code
  34. @parsed = false
  35. @stream = stream
  36. @recur = 0
  37. @recurLimit = 128
  38. end
  39. def parse!
  40. $logger.warn "The script has already been parsed." if @parsed
  41. @variables = {}
  42. parseBlock @code
  43. raise "Internal error. A recursion was not completed." if @recur != 0
  44. @parsed = true
  45. end
  46. def isParsed?
  47. return @parsed
  48. end
  49. private
  50. def generatePrefix(lengths, master, exclude)
  51. available_prefixes = []
  52. lengths.each do |len|
  53. # Generiere alle möglichen Präfixe der gegebenen Länge im Master-Präfix
  54. master.mask(len).to_range.each do |p|
  55. # Füge das Präfix nur hinzu, wenn es nicht in exclude enthalten ist
  56. available_prefixes << p.to_s #unless exclude.include?(p.to_s)
  57. end
  58. end
  59. # Wähle ein zufälliges Präfix aus den verbleibenden Präfixen aus
  60. return available_prefixes.sample
  61. end
  62. def generate args
  63. raise "No prefix lengths given." if ! args.has_key? "lengths"
  64. lengths = getValue args["lengths"]
  65. raise "The prefix lengths are not an array." if ! lengths.is_a? Array
  66. raise "No length was specified." if lengths.empty?
  67. lengths.each do |len|
  68. raise "The prefix length is not valid." if len < 1 || len > 128
  69. end
  70. raise "The master prefix was not specified." if ! args.has_key? "in"
  71. master = getValue args["in"]
  72. raise "The master prefix is not from the type prefix." if ! master.is_a? IPAddr
  73. count = 1
  74. if args.has_key? "count"
  75. count = getValue args["count"]
  76. raise "Count is not an integer" if ! count.is_a? Integer
  77. raise "The number must be greater than zero." if count == 0
  78. end
  79. exclude = []
  80. if args.has_key? "exclude"
  81. exclude = getValue args["exclude"]
  82. raise "The list of prefixes to exclude is not an array." if ! exclude.is_a? Array
  83. exclude.each do |prefix|
  84. raise "An item of the array, the list of prefixes to be excluded, is not a prefix." if ! prefix.is_a? IPAddr
  85. end
  86. end
  87. pp generatePrefix(lengths, master, exclude)
  88. end
  89. def parseVariable var, optionalName = false
  90. @recur += 1
  91. if @recur > @recurLimit
  92. raise "Recursion limit of #{@recurLimit} reached."
  93. end
  94. $logger.debug "Parse variable."
  95. raise "The variable must be of type assosicated array." if ! var.is_a? Hash
  96. name = nil
  97. if ! optionalName
  98. raise "Missing variable name." if ! var.has_key? "name"
  99. raise "Variable name of wrong type." if ! var["name"].is_a? String
  100. raise "Empty variable names are not allowed." if var["name"].blank?
  101. name = var["name"]
  102. $logger.debug "Variable name: #{name}"
  103. else
  104. $logger.debug "Variable has no name."
  105. end
  106. raise "Missing variable type." if ! var.has_key? "type"
  107. raise "Variable type of wrong type." if ! var["type"].is_a? String
  108. raise "Empty variable types are not allowed." if var["type"].blank?
  109. type = var["type"]
  110. $logger.debug "Variable type: #{type}"
  111. res = nil
  112. type, subtype = type.split("/")
  113. case type
  114. when "prefix"
  115. raise "Miss CIDR of variable." if ! var.has_key? "cidr"
  116. raise "CIDR content is of the wrong type. Expect type string." if ! var["cidr"].is_a? String
  117. raise "Unknown subtype." if subtype
  118. begin
  119. res = IPAddr.new(var["cidr"])
  120. rescue IPAddr::InvalidAddressError
  121. raise "Invalid prefix."
  122. end
  123. raise "IPv4 is obsolete and is not supported." if res.ipv4?
  124. when "prefixLength"
  125. raise "Miss value of variable." if ! var.has_key? "value"
  126. raise "prefixLength content is of the wrong type. Expect type integer." if ! var["value"].is_a? Integer
  127. raise "Prefix length must be greater than zero." if var["value"] < 0
  128. raise "Prefix length must not be greater than 128." if var["value"] > 128
  129. raise "Unknown subtype." if subtype
  130. res = var["value"]
  131. when "array"
  132. res = []
  133. raise "Miss value of variable." if ! var.has_key? "value"
  134. raise "Array content is of the wrong type. Expect type array." if ! var["value"].is_a? Array
  135. subtypeMapping = {
  136. "string" => "value",
  137. "prefixLength" => "value",
  138. "prefix" => "cidr"
  139. }
  140. raise "Subtype is not supported." if subtype && (! subtypeMapping.keys.include?(subtype))
  141. $logger.debug "Iterate through array."
  142. var["value"].each do |arrVar|
  143. if subtype
  144. newVar = {
  145. "type" => subtype,
  146. subtypeMapping[subtype] => arrVar
  147. }
  148. res << parseVariable(newVar, true)[1]
  149. else
  150. res << parseVariable(arrVar, true)[1]
  151. end
  152. end
  153. when "string"
  154. raise "Miss value of variable." if ! var.has_key? "value"
  155. raise "The value of the type String must be a string." if ! var["value"].is_a? String
  156. raise "Unknown subtype." if subtype
  157. res = var["value"]
  158. else
  159. raise "Unknown variable type."
  160. end
  161. @recur -= 1
  162. return [name, res]
  163. end
  164. def getValue var
  165. if var.is_a? String
  166. case var[0]
  167. when "$"
  168. varname = var[1..]
  169. raise "The variable #{varname} could not be found." if ! @variables.has_key? varname
  170. return @variables[varname]
  171. when "%"
  172. return var[1..]
  173. else
  174. raise "Unknown instruction to read the value."
  175. end
  176. elsif var.is_a? Hash
  177. return parseVariable(var, true)[1]
  178. elsif var.is_a? Integer
  179. return var
  180. else
  181. raise "Unknown instruction to read the value."
  182. end
  183. end
  184. def prettyPrintValue prefix, postfix, val
  185. # do a bit pretty print for known types
  186. if val.is_a? IPAddr
  187. @stream.print "#{prefix}"
  188. @stream.print "#{val.to_s}/#{val.prefix}"
  189. @stream.puts "#{postfix}"
  190. elsif val.is_a? Array
  191. val.each do |entry|
  192. prettyPrintValue prefix, postfix, entry
  193. end
  194. else
  195. @stream.print "#{prefix}"
  196. @stream.print val.to_s
  197. @stream.puts "#{postfix}"
  198. end
  199. end
  200. def prettyPrint args
  201. raise "Value to be printed not specified." if ! args.has_key? "value"
  202. raise "The value must be a string." if ! args["value"].is_a? String
  203. var = getValue args["value"]
  204. prefix = nil
  205. if args.has_key? "prefix"
  206. raise "The prefix must be a string." if ! args["prefix"].is_a? String
  207. prefix = getValue args["prefix"]
  208. raise "The prefix variable must be a string." if ! prefix.is_a? String
  209. end
  210. postfix = nil
  211. if args.has_key? "postfix"
  212. raise "The postfix must be a string." if ! args["postfix"].is_a? String
  213. postfix = getValue args["postfix"]
  214. raise "The postfix variable must be a string." if ! postfix.is_a? String
  215. end
  216. prettyPrintValue prefix, postfix, var
  217. end
  218. def includeFile args
  219. raise "File to be included not specified." if ! args.has_key? "file"
  220. filename = getValue args["file"]
  221. raise "The filename must be a string." if ! filename.is_a? String
  222. to_catch = true
  223. if args.has_key? "catch"
  224. raise "The catch parameter must be a boolean." if ! (args["catch"].is_a?(TrueClass) || args["catch"].is_a?(FalseClass))
  225. to_catch = args["catch"]
  226. end
  227. $logger.info "Include the file #{filename}."
  228. begin
  229. instructions = Interpreter.loadFile filename
  230. parseBlock instructions
  231. rescue RuntimeError => e
  232. puts "An error occurred while including the file #{filename}: #{e.message}"
  233. raise e if ! to_catch
  234. end
  235. end
  236. def forLoop args
  237. $logger.debug "In for loop."
  238. raise "The array to be iterated must be specified." if ! args.has_key? "through"
  239. array = getValue args["through"]
  240. raise "The array to be interacted by is not of type array." if ! array.is_a? Array
  241. raise "The variable name must be specified." if ! args.has_key? "as"
  242. raise "The variable name must be specified as a string." if ! args["as"].is_a? String
  243. raise "The variable name must not be empty." if args["as"].blank?
  244. varName = args["as"]
  245. raise "No instructions were given." if ! args.has_key? "do"
  246. raise "The instructions must be stored as an array." if ! args["do"].is_a? Array
  247. array.each do |value|
  248. @variables[varName] = value
  249. args["do"].each do |action|
  250. raise "An action consists of a name and arguments. Invalid input detected." if action.length < 1 || action.length > 2
  251. name = action[0]
  252. raise "The action name must be a string." if ! name.is_a? String
  253. raise "The action name must not be nothing." if name.blank?
  254. args = {}
  255. if action.length == 1
  256. $logger.debug "No arguments were passed to the action #{name}."
  257. else
  258. raise "The arguments must be of the type assosicated array." if ! action[1].is_a? Hash
  259. args = action[1]
  260. end
  261. performAction name, args
  262. end
  263. end
  264. end
  265. def performAction action, args
  266. @recur += 1
  267. if @recur > @recurLimit
  268. raise "Recursion limit of #{@recurLimit} reached."
  269. end
  270. $logger.debug "Perform action."
  271. case action
  272. when "generate"
  273. generate args
  274. when "for"
  275. forLoop args
  276. when "include"
  277. includeFile args
  278. when "print"
  279. prettyPrint args
  280. else
  281. raise "Unknown action."
  282. end
  283. @recur -= 1
  284. end
  285. def parseBlock block
  286. $logger.info "Parse variables."
  287. if block.has_key? "variables"
  288. raise "The variables were not specified as an array." if ! block["variables"].is_a? Array
  289. $logger.debug "The variables are correctly present as a type array."
  290. block["variables"].each do |var|
  291. name, value = parseVariable var
  292. @variables[name] = value
  293. end
  294. else
  295. $logger.debug "The script does not contain any variables."
  296. end
  297. if block.has_key? "actions"
  298. $logger.debug "Perform actions."
  299. raise "The actions were not specified as an array." if ! block["actions"].is_a? Array
  300. $logger.debug "The actions are correctly present as a type array."
  301. block["actions"].each do |action|
  302. raise "An action consists of a name and arguments. Invalid input detected." if action.length < 1 || action.length > 2
  303. name = action[0]
  304. raise "The action name must be a string." if ! name.is_a? String
  305. raise "The action name must not be nothing." if name.blank?
  306. args = {}
  307. if action.length == 1
  308. $logger.debug "No arguments were passed to the action #{name}."
  309. else
  310. raise "The arguments must be of the type assosicated array." if ! action[1].is_a? Hash
  311. args = action[1]
  312. end
  313. performAction name, args
  314. end
  315. else
  316. $logger.debug "The script does not contain any actions."
  317. end
  318. end
  319. end
  320. $logger.info "Load code."
  321. code = Interpreter.loadFile
  322. $logger.debug "Create interpreter."
  323. i = Interpreter.new code
  324. $logger.debug "Give instruction to parse."
  325. i.parse!