# Part of the A-A-P recipe executive: Aap commands in a recipe # Copyright (C) 2002 Stichting NLnet Labs # Permission to copy and use this file is specified in the file COPYING. # If this file is missing you can find it here: http://www.a-a-p.org/COPYING # # These are functions available to the recipe. # # Some functions are used for translated items, such as dependencies and ":" # commands. # # It's OK to do "from Commands import *", these things are supposed to be # global. # import os import os.path import re import string import copy from Depend import Depend from Dictlist import string2dictlist, get_attrdict, listitem2str from DoRead import read_recipe, recipe_dir from Error import * import Global from Process import assert_var_name, recipe_error from RecPos import rpdeepcopy from Rule import Rule from Util import * from Work import getwork, getrpstack import Cache from Message import * def aap_depend(line_nr, globals, targets, sources, cmd_line_nr, commands): """Add a dependency.""" work = getwork(globals) rpstack = getrpstack(globals, line_nr) # Expand the targets into dictlists. targetlist = string2dictlist(rpstack, aap_eval(line_nr, globals, targets, Expand(1, Expand.quote_aap))) # Parse build attributes {attr = value} zero or more times. # Variables are not expanded now but when executing the build rules. build_attr, i = get_attrdict(rpstack, None, sources, 0, 0) # Expand the sources into dictlists. sourcelist = string2dictlist(rpstack, aap_eval(line_nr, globals, sources[i:], Expand(1, Expand.quote_aap))) # Add to the global lists of dependencies. # Make a copy of the RecPos stack, so that errors in "commands" can print # the recipe stack. The last entry is going to be changed, thus it needs # to be a copy, the rest can be referenced. d = Depend(targetlist, build_attr, sourcelist, work, rpdeepcopy(getrpstack(globals), cmd_line_nr), commands) work.add_dependency(rpstack, d, commands != '') def aap_autodepend(line_nr, globals, arg, cmd_line_nr, commands): """Add a dependency check.""" work = getwork(globals) rpstack = getrpstack(globals, line_nr) if not commands: recipe_error(rpstack, _(":autodepend requires build commands")) # Parse build attributes {attr = value} zero or more times. # Variables are not expanded now but when executing the build rules. build_attr, i = get_attrdict(rpstack, None, arg, 0, 0) # Expand the other arguments into a dictlist. arglist = string2dictlist(rpstack, aap_eval(line_nr, globals, arg[i:], Expand(1, Expand.quote_aap))) # Use a rule object to store the info, a rule is just like a autodepend, # except that the a autodepend uses filetype names instead of patterns. rule = Rule([], build_attr, arglist, rpdeepcopy(getrpstack(globals), cmd_line_nr), commands) work.add_autodepend(rule) def aap_rule(line_nr, globals, targets, sources, cmd_line_nr, commands): """Add a rule.""" work = getwork(globals) rpstack = getrpstack(globals, line_nr) # Expand the targets into dictlists. targetlist = string2dictlist(rpstack, aap_eval(line_nr, globals, targets, Expand(1, Expand.quote_aap))) # Parse build attributes {attr = value} zero or more times. # Variables are not expanded now but when executing the build rules. build_attr, i = get_attrdict(rpstack, None, sources, 0, 0) # Expand the sources into dictlists. sourcelist = string2dictlist(rpstack, aap_eval(line_nr, globals, sources[i:], Expand(1, Expand.quote_aap))) rule = Rule(targetlist, build_attr, sourcelist, rpdeepcopy(getrpstack(globals), cmd_line_nr), commands) work.add_rule(rule) def aap_update(line_nr, globals, arg): """Handle ":update target ...": update target(s) now.""" work = getwork(globals) rpstack = getrpstack(globals, line_nr) from DoBuild import target_update targets = string2dictlist(rpstack, aap_eval(line_nr, globals, arg, Expand(1, Expand.quote_aap))) if len(targets) == 0: recipe_error(rpstack, _("Missing argument for :update")) for t in targets: target_update(work, work.get_node(t["name"]), 1, t) def aap_error(line_nr, globals, arg): """Handle: ":error foo bar".""" rpstack = getrpstack(globals, line_nr) recipe_error(rpstack, aap_eval(line_nr, globals, arg, Expand(0, Expand.quote_aap))) def aap_unknown(line_nr, globals, arg): """Handle: ":xxx arg". Postponed until executing the line, so that an "@if aapversion > nr" can be used.""" rpstack = getrpstack(globals, line_nr) recipe_error(rpstack, _('Unknown command: "%s"') % arg) def aap_nothere(line_nr, globals, arg): """Handle using a toplevel command in build commands. Postponed until executing the line, so that an "@if aapversion > nr" can be used.""" rpstack = getrpstack(globals, line_nr) recipe_error(fp.rpstack, _('Command cannot be used here: "%s"') % arg) # ############## start of commands used in a pipe # def _get_redir(line_nr, globals, raw_arg): """Get the redirection and pipe from the argument "raw_arg". Returns these items: 1. the argument with $VAR expanded and redirection removed 2. the file name for redirection or None 3. the mode for redirection or None ("a" for append, "w" for write). 4. a command following '|' or None When using ">file" also checks if the file doesn't exist yet.""" rpstack = getrpstack(globals, line_nr) mode = None fname = None nextcmd = None # Loop over the argument, getting one token at a time. Each token is # either non-white (possibly with quoting) or a span of white space. raw_arg_len = len(raw_arg) i = 0 # current index in raw_arg new_arg = '' # argument without redirection so far while i < raw_arg_len: t, i = get_token(raw_arg, i) # Ignore trailing white space. if i == raw_arg_len and is_white(t[0]): break # After (a span of) white space, check for redirection or pipe. # Also at the start of the argument. if new_arg == '' or is_white(t[0]): if new_arg == '': # Use the token at the start of the argument. nt = t t = '' else: # Get the token after the white space nt, i = get_token(raw_arg, i) if nt[0] == '>': # Redirection: >, >> or >! if mode: recipe_error(rpstack, _('redirection appears twice')) nt_len = len(nt) ni = 1 # index after the ">", ">>" or ">!" mode = 'w' overwrite = 0 if nt_len > 1: if nt[1] == '>': mode = 'a' ni = 2 elif nt[1] == '!': overwrite = 1 ni = 2 if ni >= nt_len: # white space after ">", get next token for fname redir = nt[:ni] if i < raw_arg_len: # Get the separating white space. nt, i = get_token(raw_arg, i) if i == raw_arg_len: recipe_error(rpstack, _('Missing file name after %s') % redir) # Get the file name nt, i = get_token(raw_arg, i) else: # fname follows immediately after ">" nt = nt[ni:] # Expand $VAR in the file name. No attributes are added. # Remove quotes from the result, it's used as a filename. fname = unquote(aap_eval(line_nr, globals, nt, Expand(0, Expand.quote_aap))) if mode == "w" and not overwrite: check_exists(rpstack, fname) # When redirection is at the start, ignore the white space # after it. if new_arg == '' and i < raw_arg_len: t, i = get_token(raw_arg, i) elif nt[0] == '|': # Pipe: the rest of the argument is another command if mode: recipe_error(rpstack, _("both redirection and '|'")) if len(nt) > 1: nextcmd = nt[1:] + raw_arg[i:] else: i = skip_white(raw_arg, i) nextcmd = raw_arg[i:] if not nextcmd: # Can't have an empty command. recipe_error(rpstack, _("Nothing follows '|'")) if nextcmd[0] != ':': # Must have an aap command here. recipe_error(rpstack, _("Missing ':' after '|'")) break else: # No redirection or pipe: add to argument new_arg = new_arg + t + nt else: # Normal token: add to argument new_arg = new_arg + t if new_arg: arg = aap_eval(line_nr, globals, new_arg, Expand(0, Expand.quote_aap)) else: arg = new_arg return arg, fname, mode, nextcmd def _aap_pipe(line_nr, globals, cmd, pipein): """Handle the command that follows a '|'.""" rpstack = getrpstack(globals, line_nr) items = string.split(cmd, None, 1) if len(items) == 1: # command without argument, append empty argument. items.append('') if items[0] == ":assign": _pipe_assign(line_nr, globals, items[1], pipein) elif items[0] == ":cat": aap_cat(line_nr, globals, items[1], pipein) elif items[0] == ":filter": _pipe_filter(line_nr, globals, items[1], pipein) elif items[0] == ":print": aap_print(line_nr, globals, items[1], pipein) elif items[0] == ":tee": _pipe_tee(line_nr, globals, items[1], pipein) else: recipe_error(rpstack, (_('Invalid command after \'|\': "%s"') % items[0])) def _pipe_assign(line_nr, globals, raw_arg, pipein): """Handle: ":assign var". Can only be used in a pipe.""" rpstack = getrpstack(globals, line_nr) assert_var_name(raw_arg, rpstack) globals[raw_arg] = pipein def aap_cat(line_nr, globals, raw_arg, pipein = None): """Handle: ":cat >foo $bar".""" rpstack = getrpstack(globals, line_nr) # get the special items out of the argument arg, fname, mode, nextcmd = _get_redir(line_nr, globals, raw_arg) # get the list of files from the remaining argument filelist = string2dictlist(rpstack, arg) if len(filelist) == 0: if pipein is None: recipe_error(rpstack, _(':cat command requires at least one file name argument')) filelist = [ {"name" : "-"} ] result = '' if mode: # Open the output file for writing try: wf = open(fname, mode) except IOError, e: recipe_error(rpstack, (_('Cannot open "%s" for writing') % fname) + str(e)) # Loop over all arguments for item in filelist: fn = item["name"] if fn == '-': # "-" argument: use pipe input if pipein is None: recipe_error(rpstack, _('Using - not after a pipe')) if nextcmd: result = result + pipein else: lines = string.split(pipein, '\n') else: # file name argument: read the file try: rf = open(fn, "r") except IOError, e: recipe_error(rpstack, (_('Cannot open "%s" for reading') % fn) + str(e)) try: lines = rf.readlines() rf.close() except IOError, e: recipe_error(rpstack, (_('Cannot read from "%s"') % fn) + str(e)) if nextcmd: # pipe output: append lines to the result for l in lines: result = result + l if mode: # file output: write lines to the file try: wf.writelines(lines) except IOError, e: recipe_error(rpstack, (_('Cannot write to "%s"') % fname) + str(e)) elif not nextcmd: # output to the terminal: print lines msg_print(lines) if mode: # close the output file try: wf.close() except IOError, e: recipe_error(rpstack, (_('Error closing "%s"') % fname) + str(e)) if nextcmd: # pipe output: execute the following command _aap_pipe(line_nr, globals, nextcmd, result) elif mode: msg_info(_('Concatenated files into "%s"') % fname) def _pipe_filter(line_nr, globals, raw_arg, pipein): """Handle: ":filter function ...". Can only be used in a pipe.""" rpstack = getrpstack(globals, line_nr) arg, fname, mode, nextcmd = _get_redir(line_nr, globals, raw_arg) # Replace "%s" with "_pipein". # TODO: make it possible to escape the %s somehow? s = string.find(arg, "%s") if s < 0: recipe_error(rpstack, _('%s missing in :filter argument')) cmd = arg[:s] + "_pipein" + arg[s + 2:] # Evaluate the expression. globals["_pipein"] = pipein try: result = str(eval(cmd, globals, globals)) except StandardError, e: recipe_error(rpstack, _(':filter command failed') + str(e)) del globals["_pipein"] if mode: # redirection: write output to a file _write2file(rpstack, fname, result, mode) elif nextcmd: # pipe output: execute next command _aap_pipe(line_nr, globals, nextcmd, result) else: # output to terminal: print the result msg_print(result) def aap_print(line_nr, globals, raw_arg, pipein = None): """Handle: ":print foo $bar".""" rpstack = getrpstack(globals, line_nr) arg, fname, mode, nextcmd = _get_redir(line_nr, globals, raw_arg) if pipein: if arg: recipe_error(rpstack, _(':print cannot have both pipe input and an argument')) arg = pipein if mode: if len(arg) == 0 or arg[-1] != '\n': arg = arg + '\n' _write2file(rpstack, fname, arg, mode) elif nextcmd: if len(arg) == 0 or arg[-1] != '\n': arg = arg + '\n' _aap_pipe(line_nr, globals, nextcmd, arg) else: msg_print(arg) def _pipe_tee(line_nr, globals, raw_arg, pipein): """Handle: ":tee fname ...". Can only be used in a pipe.""" rpstack = getrpstack(globals, line_nr) arg, fname, mode, nextcmd = _get_redir(line_nr, globals, raw_arg) # get the list of files from the remaining argument filelist = string2dictlist(rpstack, arg) if len(filelist) == 0: recipe_error(rpstack, _(':tee command requires at least one file name argument')) for f in filelist: fn = f["name"] check_exists(rpstack, fn) _write2file(rpstack, fn, pipein, "w") if mode: # redirection: write output to a file _write2file(rpstack, fname, pipein, mode) elif nextcmd: # pipe output: execute next command _aap_pipe(line_nr, globals, nextcmd, pipein) else: # output to terminal: print the result msg_print(pipein) def _write2file(rpstack, fname, str, mode): """Write string "str" to file "fname" opened with mode "mode".""" try: f = open(fname, mode) except IOError, e: recipe_error(rpstack, (_('Cannot open "%s" for writing') % fname) + str(e)) try: f.write(str) f.close() except IOError, e: recipe_error(rpstack, (_('Cannot write to "%s"') % fname) + str(e)) # ############## end of commands used in a pipe # def aap_child(line_nr, globals, arg): """Handle ":child filename": execute a recipe.""" rpstack = getrpstack(globals, line_nr) work = getwork(globals) # Get the argument and attributes. varlist = string2dictlist(rpstack, aap_eval(line_nr, globals, arg, Expand(1, Expand.quote_aap))) if len(varlist) == 0: recipe_error(rpstack, _(":child requires an argument")) if len(varlist) > 1: recipe_error(rpstack, _(":child only accepts one argument")) name = varlist[0]["name"] force_refresh = Global.cmd_args.has_option("refresh-recipe") if ((force_refresh or not os.path.exists(name)) and varlist[0].has_key("refresh")): # Need to create a node to refresh it. # Ignore errors, a check for existence is below. # Use a cached file when no forced refresh. from VersCont import refresh_node refresh_node(rpstack, globals, work.get_node(name, 0, varlist[0]), not force_refresh) if not os.path.exists(name): if varlist[0].has_key("refresh"): recipe_error(rpstack, _('Cannot download recipe "%s"') % name) recipe_error(rpstack, _('Recipe "%s" does not exist') % name) try: cwd = os.getcwd() except OSError: recipe_error(rpstack, _("Cannot obtain current directory")) name = recipe_dir(os.path.abspath(name)) # Execute the child recipe. Make a copy of the globals to avoid # the child modifies them. new_globals = globals.copy() new_globals["exports"] = {} new_globals["dependencies"] = [] read_recipe(rpstack, name, new_globals) # TODO: move dependencies from the child to the current recipe, # using the rules from the child # Move the exported variables to the globals of the current recipe exports = new_globals["exports"] for e in exports.keys(): globals[e] = exports[e] # go back to the previous current directory try: if cwd != os.getcwd(): # Note: This message is not translated, so that a parser # for the messages isn't confused by various languages. msg_changedir('Entering directory "' + cwd + '"') try: os.chdir(cwd) except OSError: recipe_error(rpstack, _('Cannot change to directory "%s"') % cwd) except OSError: recipe_error(rpstack, _("Cannot obtain current directory")) def aap_export(line_nr, globals, arg): """Export a variable to the parent recipe (if any).""" rpstack = getrpstack(globals, line_nr) varlist = string2dictlist(rpstack, aap_eval(line_nr, globals, arg, Expand(1, Expand.quote_aap))) for i in varlist: n = i["name"] assert_var_name(n, rpstack) globals["exports"][n] = get_var_val(line_nr, globals, n) def aap_attr(line_nr, globals, arg): """Add attributes to nodes.""" aap_attribute(line_nr, globals, arg) def aap_attribute(line_nr, globals, arg): """Add attributes to nodes.""" work = getwork(globals) rpstack = getrpstack(globals, line_nr) # Get the optional leading attributes. if not arg: recipe_error(rpstack, _(":attr command requires an argument")) if arg[0] == '{': attrdict, i = get_attrdict(rpstack, globals, arg, 0, 1) else: attrdict = {} i = 0 # Get the list of items. varlist = string2dictlist(rpstack, aap_eval(line_nr, globals, arg[i:], Expand(1, Expand.quote_aap))) if not varlist: recipe_error(rpstack, _(":attr command requires a file argument")) # Loop over all items, adding attributes to the node. for i in varlist: node = work.get_node(i["name"], 1, i) node.set_attributes(attrdict) def aap_assign(line_nr, globals, varname, arg, dollar, extra): """Assignment command in a recipe. "varname" is the name of the variable. "arg" is the argument value (Python expression already expanded). When "dollar" is '$' don't expand $VAR items. When "extra" is '?' only assign when "varname" wasn't set yet. When "extra" is '+' append to "varname".""" # Skip the whole assignment for "var ?= val" if var was already set. if extra != '?' or not globals.has_key(varname): if dollar != '$': # Expand variables in "arg". val = aap_eval(line_nr, globals, arg, Expand(1, Expand.quote_aap)) else: # Postpone expanding variables in "arg". Set "$var$" to remember # it has to be done when using the variable. val = arg # append or set the value if extra == '+' and globals.has_key(varname): globals[varname] = (get_var_val(line_nr, globals, varname) + ' ' + val) else: globals[varname] = val exn = '$' + varname if dollar != '$': # Stop postponing variable expansion. if globals.has_key(exn): del globals[exn] else: # Postpone expanding variables in "arg". Set "$var" to remember # it has to be done when using the variable. globals[exn] = 1 def aap_eval(line_nr, globals, arg, expand, startquote = '', skip_errors = 0): """Evaluate $VAR, $(VAR) and ${VAR} in "arg", which is a string. $VAR is expanded and the resulting string is combined with what comes before and after $VAR. text$VAR -> "textval1" "textval2". "expand" is an Expand object that specifies the way $VAR is expanded. When "startquote" isn't empty, work like "arg" was prededed by it. When "skip_errors" is non-zero, leave items with errors unexpanded, never fail. """ rpstack = getrpstack(globals, line_nr) res = '' # resulting string so far inquote = startquote # ' or " when inside quotes itemstart = 0 # index where a white separated item starts arg_len = len(arg) idx = 0 while idx < arg_len: if arg[idx] == '$': idx = idx + 1 if arg[idx] == '$': res = res + '$' # reduce $$ to a single $ idx = idx + 1 elif arg[idx] == '#': res = res + '#' # reduce $# to a single # idx = idx + 1 else: # Remember what non-white text came before the $. before = res[itemstart:] exp = copy.copy(expand) # make a copy so that we can change it # Check for type of quoting. if arg[idx] == '-': idx = idx + 1 exp.attr = 0 elif arg[idx] == '+': idx = idx + 1 exp.attr = 1 # Check for '+' (include attributes) or '-' (exclude # attributes) if arg[idx] == '=': idx = idx + 1 exp.quote = Expand.quote_none elif arg[idx] == "'": idx = idx + 1 exp.quote = Expand.quote_aap elif arg[idx] == '"': idx = idx + 1 exp.quote = Expand.quote_double elif arg[idx] == '\\': idx = idx + 1 exp.quote = Expand.quote_bs elif arg[idx] == '!': idx = idx + 1 exp.quote = Expand.quote_shell # Check for $(VAR) and ${VAR}. if arg[idx] == '(' or arg[idx] == '{': s = skip_white(arg, idx + 1) else: s = idx # get the variable name e = s while e < arg_len and varchar(arg[e]): e = e + 1 if e == s: if skip_errors: res = res + '$' continue recipe_error(rpstack, _("Invalid character after $")) varname = arg[s:e] if not globals.has_key(varname): if skip_errors: res = res + '$' continue recipe_error(rpstack, _('Unknown variable: "%s"') % varname) index = -1 if s > idx: # Inside () or {} e = skip_white(arg, e) if e < arg_len and arg[e] == '[': # get index for $(var[n]) b = e brak = 0 e = e + 1 # TODO: ignore [] inside quotes? while e < arg_len and (arg[e] != ']' or brak > 0): if arg[e] == '[': brak = brak + 1 elif arg[e] == ']': brak = brak - 1 e = e + 1 if e == arg_len or arg[e] != ']': if skip_errors: res = res + '$' continue recipe_error(rpstack, _("Missing ']'")) v = aap_eval(line_nr, globals, arg[b+1:e], Expand(0, Expand.quote_none), '', skip_errors) try: index = int(v) except: if skip_errors: res = res + '$' continue recipe_error(rpstack, _('index does not evaluate to a number: "%s"') % v) if index < 0: if skip_errors: res = res + '$' continue recipe_error(rpstack, _('index evaluates to a negative number: "%d"') % index) e = skip_white(arg, e + 1) # Check for matching () and {} if (e == arg_len or (arg[idx] == '(' and arg[e] != ')') or (arg[idx] == '{' and arg[e] != '}')): if skip_errors: res = res + '$' continue recipe_error(rpstack, _('No match for "%s"') % arg[idx]) # Continue after the () or {} idx = e + 1 else: # Continue after the varname idx = e # Find what comes after $VAR. # Need to remember whether it starts inside quotes. after_inquote = inquote s = idx while idx < arg_len: if inquote: if arg[idx] == inquote: inquote = '' elif arg[idx] == '"' or arg[idx] == "'": inquote = arg[idx] elif string.find(string.whitespace + "{", arg[idx]) != -1: break idx = idx + 1 after = arg[s:idx] if exp.attr: # Obtain any following attributes, advance to after them. # Also expand $VAR inside the attributes. attrdict, idx = get_attrdict(rpstack, globals, arg, idx, 1) else: attrdict = {} if before == '' and after == '' and len(attrdict) == 0: if index < 0: # No rc-style expansion or index, use the value of # $VAR as specified with quote-expansion try: res = res + get_var_val(line_nr, globals, varname, exp) except TypeError: if skip_errors: res = res + '$' continue recipe_error(rpstack, _('Type of variable "%s" must be a string') % varname) else: # No rc-style expansion but does have an index. # Get the Dictlist of the referred variable. varlist = string2dictlist(rpstack, get_var_val(line_nr, globals, varname)) if len(varlist) < index + 1: msg_warning( _('Index "%d" is out of range for variable "%s"') % (index, varname)) else: res = res + expand_item(varlist[index], exp) idx = s else: # rc-style expansion of a variable # Get the Dictlist of the referred variable. # When an index is specified us that entry of the list. # When index is out of range or the list is empty, use a # list with one empty entry. varlist1 = string2dictlist(rpstack, get_var_val(line_nr, globals, varname)) if (len(varlist1) == 0 or (index >= 0 and len(varlist1) < index + 1)): if index >= 0: msg_warning( _('Index "%d" is out of range for variable "%s"') % (index, varname)) varlist1 = [{"name": ""}] elif index >= 0: varlist1 = [ varlist1[index] ] # Evaluate the "after" of $(VAR)after {attr = val}. varlist2 = string2dictlist(rpstack, aap_eval(line_nr, globals, after, Expand(1, Expand.quote_aap), startquote = after_inquote), startquote = after_inquote) if len(varlist2) == 0: varlist2 = [{"name": ""}] # Remove quotes from "before", they are put back when # needed. lead = '' q = '' for c in before: if q: if c == q: q = '' else: lead = lead + c elif c == '"' or c == "'": q = c else: lead = lead + c # Combine "before", the list from $VAR, the list from # "after" and the following attributes. # Put "startquote" in front, because the terminalting quote # will have been removed. rcs = startquote startquote = '' for d1 in varlist1: for d2 in varlist2: if rcs: rcs = rcs + ' ' s = lead + d1["name"] + d2["name"] # If $VAR was in quotes put the result in quotes. if after_inquote: rcs = rcs + enquote(s, quote = after_inquote) else: rcs = rcs + expand_itemstr(s, exp) if exp.attr: for k in d1.keys(): if k != "name": rcs = rcs + "{%s = %s}" % (k, d1[k]) for k in d2.keys(): if k != "name": rcs = rcs + "{%s = %s}" % (k, d2[k]) for k in attrdict.keys(): rcs = rcs + "{%s = %s}" % (k, attrdict[k]) res = res[0:itemstart] + rcs else: # No '$' at this position, include the character in the result. # Check if quoting starts or ends and whether white space separates # an item, this is used for expanding $VAR. if inquote: if arg[idx] == inquote: inquote = '' elif arg[idx] == '"' or arg[idx] == "'": inquote = arg[idx] elif is_white(arg[idx]): itemstart = len(res) + 1 res = res + arg[idx] idx = idx + 1 return res def expr2str(item): """Used to turn the result of a Python expression into a string. For a list the elements are separated with a space. Dollars are doubled to avoid them being recognized as variables.""" import types if type(item) == types.ListType: s = '' for i in item: if s: s = s + ' ' s = s + listitem2str(str(i)) else: s = str(item) return string.replace(s, '$', '$$') def aap_sufreplace(suffrom, sufto, string): """Replace suffixes in "string" from "suffrom" to "sufto".""" return re.sub(string.replace(suffrom, ".", "\\.") + "\\b", sufto, string) def aap_shell(line_nr, globals, cmds): """Execute shell commands from the recipe.""" s = aap_eval(line_nr, globals, cmds, Expand(0, Expand.quote_shell)) if globals.has_key("target"): msg_extra(_('Shell commands for updating "%s":') % globals["target"]) n = logged_system(s) if n != 0: recipe_error(getrpstack(globals, line_nr), _("Shell returned %d when executing:\n%s") % (n, s)) def aap_system(line_nr, globals, cmds): """Implementation of ":system cmds". Almost the same as aap_shell().""" aap_shell(line_nr, globals, cmds + '\n') def aap_sys(line_nr, globals, cmds): """Implementation of ":sys cmds". Almost the same as aap_shell().""" aap_shell(line_nr, globals, cmds + '\n') def aap_copy(line_nr, globals, arg): """Implementation of ":copy -x from to".""" # It's in a separate module, it's quite a bit of stuff. from CopyMove import copy_move copy_move(line_nr, globals, arg, 1) def aap_move(line_nr, globals, arg): """Implementation of ":move -x from to".""" # It's in a separate module, it's quite a bit of stuff. from CopyMove import copy_move copy_move(line_nr, globals, arg, 0) def aap_delete(line_nr, globals, raw_arg): """Alias for aap_del().""" aap_del(line_nr, globals, raw_arg) def aap_del(line_nr, globals, raw_arg): """Implementation of ":del -r file1 file2".""" # Evaluate $VAR things arg = aap_eval(line_nr, globals, raw_arg, Expand(0, Expand.quote_aap)) rpstack = getrpstack(globals, line_nr) # flags: # -f don't fail when not exists # -q quiet # -r -R recursive, delete directories try: flags, i = get_flags(arg, 0, "fqrR") except UserError, e: recipe_error(rpstack, e) if 'r' in flags or 'R' in flags: recursive = 1 else: recursive = 0 # Get the remaining arguments, should be at least one. arglist = string2dictlist(rpstack, arg[i:]) if not arglist: recipe_error(rpstack, _(":del command requires an argument")) import glob from urlparse import urlparse def deltree(dir): """Recursively delete a directory or a file.""" if os.path.isdir(dir): fl = glob.glob(os.path.join(dir, "*")) for f in fl: deltree(f) os.rmdir(dir) else: os.remove(dir) for a in arglist: fname = a["name"] scheme, mach, path, parm, query, frag = urlparse(fname, '', 0) if scheme != '': recipe_error(rpstack, _('Cannot delete remotely yet: "%s"') % fname) # Expand ~user and wildcards. fl = glob.glob(os.path.expanduser(fname)) if len(fl) == 0 and not 'f' in flags: recipe_error(rpstack, _('No match for "%s"') % fname) for f in fl: try: if recursive: deltree(f) else: os.remove(f) except EnvironmentError, e: recipe_error(rpstack, (_('Could not delete "%s"') % f) + str(e)) else: if os.path.exists(f): recipe_error(rpstack, _('Could not delete "%s"') % f) if not 'q' in flags: msg_info(_('Deleted "%s"') % fname) def aap_mkdir(line_nr, globals, raw_arg): """Implementation of ":mkdir dir1 dir2".""" # Evaluate $VAR things arg = aap_eval(line_nr, globals, raw_arg, Expand(0, Expand.quote_aap)) rpstack = getrpstack(globals, line_nr) # flags: # -f create file when it does not exist try: flags, i = get_flags(arg, 0, "f") except UserError, e: recipe_error(rpstack, e) # Get the arguments, should be at least one. arglist = string2dictlist(rpstack, arg[i:]) if not arglist: recipe_error(rpstack, _(":mkdir command requires an argument")) from urlparse import urlparse for a in arglist: name = a["name"] scheme, mach, path, parm, query, frag = urlparse(name, '', 0) if scheme != '': recipe_error(rpstack, _('Cannot create remote directory yet: "%s"') % name) # Expand ~user, create directory dir = os.path.expanduser(name) # Skip creation when it already exists. if not ('f' in flags and os.path.isdir(dir)): try: os.mkdir(dir) except EnvironmentError, e: recipe_error(rpstack, (_('Could not create directory "%s"') % dir) + str(e)) def aap_touch(line_nr, globals, raw_arg): """Implementation of ":touch file1 file2".""" # Evaluate $VAR things arg = aap_eval(line_nr, globals, raw_arg, Expand(0, Expand.quote_aap)) rpstack = getrpstack(globals, line_nr) # flags: # -f create file when it does not exist try: flags, i = get_flags(arg, 0, "f") except UserError, e: recipe_error(rpstack, e) # Get the arguments, should be at least one. arglist = string2dictlist(rpstack, arg[i:]) if not arglist: recipe_error(rpstack, _(":touch command requires an argument")) from urlparse import urlparse import time for a in arglist: name = a["name"] scheme, mach, path, parm, query, frag = urlparse(name, '', 0) if scheme != '': recipe_error(rpstack, _('Cannot touch remote file yet: "%s"') % name) # Expand ~user, touch file name = os.path.expanduser(name) if os.path.exists(name): now = time.time() try: os.utime(name, (now, now)) except EnvironmentError, e: recipe_error(rpstack, (_('Could not update time of "%s"') % name) + str(e)) else: if not 'f' in flags: recipe_error(rpstack, _('"%s" does not exist (use :touch -f to create it)') % name) try: # create an empty file f = os.open(name, os.O_WRONLY + os.O_CREAT + os.O_EXCL) os.close(f) except EnvironmentError, e: recipe_error(rpstack, (_('Could not create "%s"') % name) + str(e)) def flush_cache(): """Called just before setting $CACHE.""" # It's here so that only this module has to be imported in Process.py. Cache.dump_cache() # dictionary of recipes that have been refreshed (using full path name). recipe_refreshed = {} def aap_include(line_nr, globals, arg): """Handle ":include filename": read the recipe into the current globals.""" work = getwork(globals) rpstack = getrpstack(globals, line_nr) # Evaluate the arguments args = string2dictlist(rpstack, aap_eval(line_nr, globals, arg, Expand(1, Expand.quote_aap))) if len(args) != 1: recipe_error(rpstack, _(":include requires one argument")) recname = args[0]["name"] # Refresh the recipe when invoked with the "-R" argument. if ((Global.cmd_args.has_option("refresh-recipe") or not os.path.exists(recname)) and args[0].has_key("refresh")): fullname = full_fname(recname) if not recipe_refreshed.has_key(fullname): from VersCont import refresh_node # Create a node for the recipe and refresh it. node = work.get_node(recname, 0, args[0]) if not refresh_node(rpstack, globals, node, 0): msg_warning(_('Could not update recipe "%s"') % recname) # Also mark it as updated when it failed, don't try again. recipe_refreshed[fullname] = 1 read_recipe(rpstack, recname, globals) def do_recipe_cmd(rpstack): """Return non-zero if a ":recipe" command in the current recipe may be executed.""" # Return right away when not invoked with the "-R" argument. if not Global.cmd_args.has_option("refresh-recipe"): return 0 # Skip when this recipe was already updated. recname = full_fname(rpstack[-1].name) if recipe_refreshed.has_key(recname): return 0 return 1 def aap_recipe(line_nr, globals, arg): """Handle ":recipe {refresh = name_list}": may download this recipe.""" work = getwork(globals) rpstack = getrpstack(globals, line_nr) # Return right away when not to be executed. if not do_recipe_cmd(rpstack): return # Register the recipe to have been updated. Also when it failed, don't # want to try again. recname = full_fname(rpstack[-1].name) recipe_refreshed[recname] = 1 short_name = shorten_name(recname) msg_info(_('Updating recipe "%s"') % short_name) orgdict, i = get_attrdict(rpstack, globals, arg, 0, 1) if not orgdict.has_key("refresh"): recipe_error(rpstack, _(":recipe requires a refresh attribute")) # TODO: warning for trailing characters? from VersCont import refresh_node # Create a node for the recipe and refresh it. node = work.get_node(short_name, 0, orgdict) if refresh_node(rpstack, globals, node, 0): # TODO: check if the recipe was completely read # TODO: no need for restart if the recipe didn't change # Restore the globals to the values from when starting to read the # recipe. start_globals = globals["_start_globals"] for k in globals.keys(): if not start_globals.has_key(k): del globals[k] for k in start_globals.keys(): globals[k] = start_globals[k] # read the new recipe file read_recipe(rpstack, recname, globals, reread = 1) # Throw an exception to cancel executing the rest of the script # generated from the old recipe. This is catched in read_recipe() raise OriginUpdate msg_warning(_('Could not update recipe "%s"') % node.name) # # Generic function for getting the arguments of :refresh, :checkout, :commit, # :checkin, :unlock and :publish # def get_verscont_args(line_nr, globals, arg, cmd): """"Handle ":cmd {attr = } file ...".""" rpstack = getrpstack(globals, line_nr) # Get the optional attributes that apply to all arguments. attrdict, i = get_attrdict(rpstack, globals, arg, 0, 1) # evaluate the arguments into a dictlist varlist = string2dictlist(rpstack, aap_eval(line_nr, globals, arg[i:], Expand(1, Expand.quote_aap))) if not varlist: recipe_error(rpstack, _(':%s requires an argument') % cmd) return attrdict, varlist def do_verscont_cmd(rpstack, globals, action, attrdict, varlist): """Perform "action" on items in "varlist", using attributes in "attrdict".""" from VersCont import verscont_node, refresh_node work = getwork(globals) for item in varlist: node = work.get_node(item["name"], 1, item) node.set_attributes(attrdict) if action == "refresh": r = (node.may_refresh() and refresh_node(rpstack, globals, node, 0) == 0) elif action == "publish": r = publish_node(rpstack, globals, node) else: r = verscont_node(rpstack, globals, node, action) if not r: msg_warning(_('%s failed for "%s"') % (action, item["name"])) def verscont_cmd(line_nr, globals, arg, action): """Perform "action" for each item "varlist".""" rpstack = getrpstack(globals, line_nr) attrdict, varlist = get_verscont_args(line_nr, globals, arg, action) do_verscont_cmd(rpstack, globals, action, attrdict, varlist) def aap_refresh(line_nr, globals, arg): """"Handle ":refresh {attr = val} file ...".""" verscont_cmd(line_nr, globals, arg, "refresh") def aap_checkout(line_nr, globals, arg): """"Handle ":checkout {attr = val} file ...".""" verscont_cmd(line_nr, globals, arg, "checkout") def aap_commit(line_nr, globals, arg): """"Handle ":commit {attr = val} file ...".""" verscont_cmd(line_nr, globals, arg, "commit") def aap_checkin(line_nr, globals, arg): """"Handle ":checkin {attr = val} file ...".""" verscont_cmd(line_nr, globals, arg, "checkin") def aap_unlock(line_nr, globals, arg): """"Handle ":unlock {attr = val} file ...".""" verscont_cmd(line_nr, globals, arg, "unlock") def aap_publish(line_nr, globals, arg): """"Handle ":publish {attr = val} file ...".""" verscont_cmd(line_nr, globals, arg, "publish") def aap_add(line_nr, globals, arg): """"Handle ":add {attr = val} file ...".""" verscont_cmd(line_nr, globals, arg, "add") def aap_remove(line_nr, globals, arg): """"Handle ":remove {attr = val} file ...".""" verscont_cmd(line_nr, globals, arg, "remove") def aap_verscont(line_nr, globals, arg): """"Handle ":verscont action {attr = val} [file ...]".""" rpstack = getrpstack(globals, line_nr) # evaluate the arguments into a dictlist varlist = string2dictlist(rpstack, aap_eval(line_nr, globals, arg, Expand(1, Expand.quote_aap))) if not varlist: recipe_error(rpstack, _(':verscont requires an argument')) if len(varlist) > 1: arglist = varlist[1:] else: arglist = [] do_verscont_cmd(rpstack, globals, varlist[0]["name"], varlist[0], arglist) def aap_commitall(line_nr, globals, arg): """"Handle ":commitall {attr = val} file ...".""" attrdict, varlist = get_verscont_args(line_nr, globals, arg, "commitall") recipe_error(rpstack, _('Sorry, :commitall is not implemented yet')) def aap_publishall(line_nr, globals, arg): """"Handle ":publishall {attr = val} file ...".""" attrdict, varlist = get_verscont_args(line_nr, globals, arg, "publishall") recipe_error(rpstack, _('Sorry, :publishall is not implemented yet')) def aap_removeall(line_nr, globals, arg): """"Handle ":removeall {attr = val} [dir ...]".""" rpstack = getrpstack(globals, line_nr) # flags: # -l local (non-recursive) # -r recursive try: flags, i = get_flags(arg, 0, "lr") except UserError, e: recipe_error(rpstack, e) # Get the optional attributes that apply to all arguments. attrdict, i = get_attrdict(rpstack, globals, arg, i, 1) # evaluate the arguments into a dictlist varlist = string2dictlist(rpstack, aap_eval(line_nr, globals, arg[i:], Expand(1, Expand.quote_aap))) from VersCont import verscont_removeall if varlist: # Directory name arguments: Do each directory non-recursively for dir in varlist: for k in attrdict.keys(): dir[k] = attrdict[k] verscont_removeall(rpstack, globals, dir, 'r' in flags) else: # No arguments: Do current directory recursively attrdict["name"] = "." verscont_removeall(rpstack, globals, attrdict, not 'l' in flags) def aap_filetype(line_nr, globals, arg, cmd_line_nr, commands): """Add filetype detection from a file or in-line detection rules.""" from Filetype import ft_add_rules, ft_read_file, DetectError rpstack = getrpstack(globals, line_nr) # look through the arguments args = string2dictlist(rpstack, aap_eval(line_nr, globals, arg, Expand(0, Expand.quote_aap))) if len(args) > 1: recipe_error(rpstack, _('Too many arguments for :filetype')) if len(args) == 1 and commands: recipe_error(rpstack, _('Cannot have file name and commands for :filetype')) if len(args) == 0 and not commands: recipe_error(rpstack, _('Must have file name or commands for :filetype')) try: if commands: what = "lines" ft_add_rules(commands) else: fname = args[0]["name"] what = 'file "%s"' % fname ft_read_file(fname) except DetectError, e: recipe_error(rpstack, (_('Error in detection %s: ') % what) + str(e)) # vim: set sw=4 sts=4 tw=79 fo+=l: .