1 """SCons.Action
2
3 This encapsulates information about executing any sort of action that
4 can build one or more target Nodes (typically files) from one or more
5 source Nodes (also typically files) given a specific Environment.
6
7 The base class here is ActionBase. The base class supplies just a few
8 OO utility methods and some generic methods for displaying information
9 about an Action in response to the various commands that control printing.
10
11 A second-level base class is _ActionAction. This extends ActionBase
12 by providing the methods that can be used to show and perform an
13 action. True Action objects will subclass _ActionAction; Action
14 factory class objects will subclass ActionBase.
15
16 The heavy lifting is handled by subclasses for the different types of
17 actions we might execute:
18
19 CommandAction
20 CommandGeneratorAction
21 FunctionAction
22 ListAction
23
24 The subclasses supply the following public interface methods used by
25 other modules:
26
27 __call__()
28 THE public interface, "calling" an Action object executes the
29 command or Python function. This also takes care of printing
30 a pre-substitution command for debugging purposes.
31
32 get_contents()
33 Fetches the "contents" of an Action for signature calculation.
34 This is what gets MD5 checksumm'ed to decide if a target needs
35 to be rebuilt because its action changed.
36
37 genstring()
38 Returns a string representation of the Action *without*
39 command substitution, but allows a CommandGeneratorAction to
40 generate the right action based on the specified target,
41 source and env. This is used by the Signature subsystem
42 (through the Executor) to obtain an (imprecise) representation
43 of the Action operation for informative purposes.
44
45
46 Subclasses also supply the following methods for internal use within
47 this module:
48
49 __str__()
50 Returns a string approximation of the Action; no variable
51 substitution is performed.
52
53 execute()
54 The internal method that really, truly, actually handles the
55 execution of a command or Python function. This is used so
56 that the __call__() methods can take care of displaying any
57 pre-substitution representations, and *then* execute an action
58 without worrying about the specific Actions involved.
59
60 strfunction()
61 Returns a substituted string representation of the Action.
62 This is used by the _ActionAction.show() command to display the
63 command/function that will be executed to generate the target(s).
64
65 There is a related independent ActionCaller class that looks like a
66 regular Action, and which serves as a wrapper for arbitrary functions
67 that we want to let the user specify the arguments to now, but actually
68 execute later (when an out-of-date check determines that it's needed to
69 be executed, for example). Objects of this class are returned by an
70 ActionFactory class that provides a __call__() method as a convenient
71 way for wrapping up the functions.
72
73 """
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 __revision__ = "src/engine/SCons/Action.py 3603 2008/10/10 05:46:45 scons"
99
100 import cPickle
101 import dis
102 import os
103 import os.path
104 import string
105 import sys
106 import subprocess
107
108 from SCons.Debug import logInstanceCreation
109 import SCons.Errors
110 import SCons.Executor
111 import SCons.Util
112
115
116 _null = _Null
117
118 print_actions = 1
119 execute_actions = 1
120 print_actions_presub = 0
121
123 try:
124 return n.rfile()
125 except AttributeError:
126 return n
127
130
131 try:
132 SET_LINENO = dis.SET_LINENO
133 HAVE_ARGUMENT = dis.HAVE_ARGUMENT
134 except AttributeError:
135 remove_set_lineno_codes = lambda x: x
136 else:
152
153
155 """Return the signature contents of a callable Python object.
156 """
157 try:
158
159 return _function_contents(obj.im_func)
160
161 except AttributeError:
162 try:
163
164 return _function_contents(obj.__call__.im_func)
165
166 except AttributeError:
167 try:
168
169 return _code_contents(obj)
170
171 except AttributeError:
172
173 return _function_contents(obj)
174
175
177 """Return the signature contents of any Python object.
178
179 We have to handle the case where object contains a code object
180 since it can be pickled directly.
181 """
182 try:
183
184 return _function_contents(obj.im_func)
185
186 except AttributeError:
187 try:
188
189 return _function_contents(obj.__call__.im_func)
190
191 except AttributeError:
192 try:
193
194 return _code_contents(obj)
195
196 except AttributeError:
197 try:
198
199 return _function_contents(obj)
200
201 except AttributeError:
202
203 try:
204 return cPickle.dumps(obj)
205 except (cPickle.PicklingError, TypeError):
206
207
208
209
210
211 return str(obj)
212
213
214 -def _code_contents(code):
215 """Return the signature contents of a code object.
216
217 By providing direct access to the code object of the
218 function, Python makes this extremely easy. Hooray!
219
220 Unfortunately, older versions of Python include line
221 number indications in the compiled byte code. Boo!
222 So we remove the line number byte codes to prevent
223 recompilations from moving a Python function.
224 """
225
226 contents = []
227
228
229
230 contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames)))
231 try:
232 contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars)))
233 except AttributeError:
234
235 contents.append(",0,0")
236
237
238
239
240
241
242
243
244
245 contents.append(',(' + string.join(map(_object_contents,code.co_consts[1:]),',') + ')')
246
247
248
249
250
251 contents.append(',(' + string.join(map(_object_contents,code.co_names),',') + ')')
252
253
254
255 contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')')
256
257 return string.join(contents, '')
258
259
261 """Return the signature contents of a function."""
262
263 contents = [_code_contents(func.func_code)]
264
265
266 if func.func_defaults:
267 contents.append(',(' + string.join(map(_object_contents,func.func_defaults),',') + ')')
268 else:
269 contents.append(',()')
270
271
272 try:
273 closure = func.func_closure or []
274 except AttributeError:
275
276 closure = []
277
278
279 try:
280 xxx = map(lambda x: _object_contents(x.cell_contents), closure)
281 except AttributeError:
282 xxx = []
283 contents.append(',(' + string.join(xxx, ',') + ')')
284
285 return string.join(contents, '')
286
287
289
290
291
292 a1 = Action(act1)
293 a2 = Action(act2)
294 if a1 is None or a2 is None:
295 raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2))
296 if isinstance(a1, ListAction):
297 if isinstance(a2, ListAction):
298 return ListAction(a1.list + a2.list)
299 else:
300 return ListAction(a1.list + [ a2 ])
301 else:
302 if isinstance(a2, ListAction):
303 return ListAction([ a1 ] + a2.list)
304 else:
305 return ListAction([ a1, a2 ])
306
308 """This is the actual "implementation" for the
309 Action factory method, below. This handles the
310 fact that passing lists to Action() itself has
311 different semantics than passing lists as elements
312 of lists.
313
314 The former will create a ListAction, the latter
315 will create a CommandAction by converting the inner
316 list elements to strings."""
317
318 if isinstance(act, ActionBase):
319 return act
320 if SCons.Util.is_List(act):
321 return apply(CommandAction, (act,)+args, kw)
322 if callable(act):
323 try:
324 gen = kw['generator']
325 del kw['generator']
326 except KeyError:
327 gen = 0
328 if gen:
329 action_type = CommandGeneratorAction
330 else:
331 action_type = FunctionAction
332 return apply(action_type, (act,)+args, kw)
333 if SCons.Util.is_String(act):
334 var=SCons.Util.get_environment_var(act)
335 if var:
336
337
338
339
340
341
342 return apply(LazyAction, (var,)+args, kw)
343 commands = string.split(str(act), '\n')
344 if len(commands) == 1:
345 return apply(CommandAction, (commands[0],)+args, kw)
346 else:
347 listCmdActions = map(lambda x, args=args, kw=kw:
348 apply(CommandAction, (x,)+args, kw),
349 commands)
350 return ListAction(listCmdActions)
351 return None
352
354 """A factory for action objects."""
355 if SCons.Util.is_List(act):
356 acts = map(lambda a, args=args, kw=kw:
357 apply(_do_create_action, (a,)+args, kw),
358 act)
359 acts = filter(None, acts)
360 if len(acts) == 1:
361 return acts[0]
362 else:
363 return ListAction(acts)
364 else:
365 return apply(_do_create_action, (act,)+args, kw)
366
368 """Base class for all types of action objects that can be held by
369 other objects (Builders, Executors, etc.) This provides the
370 common methods for manipulating and combining those actions."""
371
373 return cmp(self.__dict__, other)
374
377
379 return _actionAppend(self, other)
380
382 return _actionAppend(other, self)
383
385
386
387
388
389
390
391 self.presub_env = env
392 lines = string.split(str(self), '\n')
393 self.presub_env = None
394 return lines
395
396 - def get_executor(self, env, overrides, tlist, slist, executor_kw):
397 """Return the Executor for this Action."""
398 return SCons.Executor.Executor(self, env, overrides,
399 tlist, slist, executor_kw)
400
402 """Base class for actions that create output objects."""
403 - def __init__(self, strfunction=_null, presub=_null, chdir=None, exitstatfunc=None, **kw):
411
413 sys.stdout.write(s + "\n")
414
421 if not SCons.Util.is_List(target):
422 target = [target]
423 if not SCons.Util.is_List(source):
424 source = [source]
425
426 if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
427 if presub is _null:
428 presub = self.presub
429 if presub is _null:
430 presub = print_actions_presub
431 if show is _null: show = print_actions
432 if execute is _null: execute = execute_actions
433 if chdir is _null: chdir = self.chdir
434 save_cwd = None
435 if chdir:
436 save_cwd = os.getcwd()
437 try:
438 chdir = str(chdir.abspath)
439 except AttributeError:
440 if not SCons.Util.is_String(chdir):
441 chdir = str(target[0].dir)
442 if presub:
443 t = string.join(map(str, target), ' and ')
444 l = string.join(self.presub_lines(env), '\n ')
445 out = "Building %s with action:\n %s\n" % (t, l)
446 sys.stdout.write(out)
447 s = None
448 if show and self.strfunction:
449 s = self.strfunction(target, source, env)
450 if s:
451 if chdir:
452 s = ('os.chdir(%s)\n' % repr(chdir)) + s
453 try:
454 get = env.get
455 except AttributeError:
456 print_func = self.print_cmd_line
457 else:
458 print_func = get('PRINT_CMD_LINE_FUNC')
459 if not print_func:
460 print_func = self.print_cmd_line
461 print_func(s, target, source, env)
462 stat = 0
463 if execute:
464 if chdir:
465 os.chdir(chdir)
466 try:
467 stat = self.execute(target, source, env)
468 if isinstance(stat, SCons.Errors.BuildError):
469 s = exitstatfunc(stat.status)
470 if s:
471 stat.status = s
472 else:
473 stat = s
474 else:
475 stat = exitstatfunc(stat)
476 finally:
477 if save_cwd:
478 os.chdir(save_cwd)
479 if s and save_cwd:
480 print_func('os.chdir(%s)' % repr(save_cwd), target, source, env)
481
482 return stat
483
484
486 """Takes a list of command line arguments and returns a pretty
487 representation for printing."""
488 cl = []
489 for arg in map(str, cmd_list):
490 if ' ' in arg or '\t' in arg:
491 arg = '"' + arg + '"'
492 cl.append(arg)
493 return string.join(cl)
494
495
496
497
498 default_ENV = None
499
500 -def _subproc(env, cmd, error = 'ignore', **kw):
501 """Do setup for a subprocess.Popen() call"""
502
503
504 try:
505 ENV = env['ENV']
506 except KeyError:
507 global default_ENV
508 if default_ENV is None:
509
510
511
512 default_ENV = SCons.Environment.Environment(tools=[])['ENV']
513 ENV = default_ENV
514
515
516 new_env = {}
517
518 is_String = SCons.Util.is_String
519 for key, value in ENV.items():
520 if is_String(value):
521
522
523 new_env[key] = str(value)
524 elif SCons.Util.is_List(value):
525
526
527
528 value = SCons.Util.flatten_sequence(value)
529 ENV[key] = string.join(map(str, value), os.pathsep)
530 else:
531
532
533
534
535 ENV[key] = str(value)
536 kw['env'] = new_env
537
538 try:
539
540 return apply(subprocess.Popen, (cmd,), kw)
541 except EnvironmentError, e:
542 if error == 'raise': raise
543
544 class popen:
545 def __init__(self, e): self.exception = e
546 def communicate(self): return ('','')
547 def wait(self): return -self.exception.errno
548 stdin = None
549 class f:
550 def read(self): return ''
551 def readline(self): return ''
552 stdout = stderr = f()
553 return popen(e)
554
556 """Class for command-execution actions."""
557 - def __init__(self, cmd, cmdstr=None, *args, **kw):
558
559
560
561
562
563
564
565
566
567 if __debug__: logInstanceCreation(self, 'Action.CommandAction')
568
569 if not cmdstr is None:
570 if callable(cmdstr):
571 args = (cmdstr,)+args
572 elif not SCons.Util.is_String(cmdstr):
573 raise SCons.Errors.UserError(\
574 'Invalid command display variable type. ' \
575 'You must either pass a string or a callback which ' \
576 'accepts (target, source, env) as parameters.')
577
578 apply(_ActionAction.__init__, (self,)+args, kw)
579 if SCons.Util.is_List(cmd):
580 if filter(SCons.Util.is_List, cmd):
581 raise TypeError, "CommandAction should be given only " \
582 "a single command"
583 self.cmd_list = cmd
584 self.cmdstr = cmdstr
585
587 if SCons.Util.is_List(self.cmd_list):
588 return string.join(map(str, self.cmd_list), ' ')
589 return str(self.cmd_list)
590
591 - def process(self, target, source, env):
592 result = env.subst_list(self.cmd_list, 0, target, source)
593 silent = None
594 ignore = None
595 while 1:
596 try: c = result[0][0][0]
597 except IndexError: c = None
598 if c == '@': silent = 1
599 elif c == '-': ignore = 1
600 else: break
601 result[0][0] = result[0][0][1:]
602 try:
603 if not result[0][0]:
604 result[0] = result[0][1:]
605 except IndexError:
606 pass
607 return result, ignore, silent
608
610 if not self.cmdstr is None:
611 from SCons.Subst import SUBST_RAW
612 c = env.subst(self.cmdstr, SUBST_RAW, target, source)
613 if c:
614 return c
615 cmd_list, ignore, silent = self.process(target, source, env)
616 if silent:
617 return ''
618 return _string_from_cmd_list(cmd_list[0])
619
620 - def execute(self, target, source, env):
621 """Execute a command action.
622
623 This will handle lists of commands as well as individual commands,
624 because construction variable substitution may turn a single
625 "command" into a list. This means that this class can actually
626 handle lists of commands, even though that's not how we use it
627 externally.
628 """
629 from SCons.Subst import escape_list
630 import SCons.Util
631 flatten_sequence = SCons.Util.flatten_sequence
632 is_String = SCons.Util.is_String
633 is_List = SCons.Util.is_List
634
635 try:
636 shell = env['SHELL']
637 except KeyError:
638 raise SCons.Errors.UserError('Missing SHELL construction variable.')
639
640 try:
641 spawn = env['SPAWN']
642 except KeyError:
643 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
644 else:
645 if is_String(spawn):
646 spawn = env.subst(spawn, raw=1, conv=lambda x: x)
647
648 escape = env.get('ESCAPE', lambda x: x)
649
650 try:
651 ENV = env['ENV']
652 except KeyError:
653 global default_ENV
654 if not default_ENV:
655 import SCons.Environment
656 default_ENV = SCons.Environment.Environment()['ENV']
657 ENV = default_ENV
658
659
660 for key, value in ENV.items():
661 if not is_String(value):
662 if is_List(value):
663
664
665
666 value = flatten_sequence(value)
667 ENV[key] = string.join(map(str, value), os.pathsep)
668 else:
669
670
671
672
673 ENV[key] = str(value)
674
675 cmd_list, ignore, silent = self.process(target, map(rfile, source), env)
676
677
678 for cmd_line in filter(len, cmd_list):
679
680 cmd_line = escape_list(cmd_line, escape)
681 result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
682 if not ignore and result:
683 msg = "Error %s" % result
684 return SCons.Errors.BuildError(errstr=msg,
685 status=result,
686 action=self,
687 command=cmd_line)
688 return 0
689
690 - def get_contents(self, target, source, env):
691 """Return the signature contents of this action's command line.
692
693 This strips $(-$) and everything in between the string,
694 since those parts don't affect signatures.
695 """
696 from SCons.Subst import SUBST_SIG
697 cmd = self.cmd_list
698 if SCons.Util.is_List(cmd):
699 cmd = string.join(map(str, cmd))
700 else:
701 cmd = str(cmd)
702 return env.subst_target_source(cmd, SUBST_SIG, target, source)
703
705 icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
706 if SCons.Util.is_String(icd) and icd[:1] == '$':
707 icd = env.subst(icd)
708 if not icd or icd in ('0', 'None'):
709 return []
710 from SCons.Subst import SUBST_SIG
711 cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source)
712 res = []
713 for cmd_line in cmd_list:
714 if cmd_line:
715 d = env.WhereIs(str(cmd_line[0]))
716 if d:
717 res.append(env.fs.File(d))
718 return res
719
721 """Class for command-generator actions."""
722 - def __init__(self, generator, *args, **kw):
723 if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction')
724 self.generator = generator
725 self.gen_args = args
726 self.gen_kw = kw
727
728 - def _generate(self, target, source, env, for_signature):
729
730
731 if not SCons.Util.is_List(target):
732 target = [target]
733
734 ret = self.generator(target=target, source=source, env=env, for_signature=for_signature)
735 gen_cmd = apply(Action, (ret,)+self.gen_args, self.gen_kw)
736 if not gen_cmd:
737 raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret))
738 return gen_cmd
739
741 try:
742 env = self.presub_env
743 except AttributeError:
744 env = None
745 if env is None:
746 env = SCons.Defaults.DefaultEnvironment()
747 act = self._generate([], [], env, 1)
748 return str(act)
749
752
755 act = self._generate(target, source, env, 0)
756 return act(target, source, env, exitstatfunc, presub,
757 show, execute, chdir)
758
759 - def get_contents(self, target, source, env):
760 """Return the signature contents of this action's command line.
761
762 This strips $(-$) and everything in between the string,
763 since those parts don't affect signatures.
764 """
765 return self._generate(target, source, env, 1).get_contents(target, source, env)
766
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789 -class LazyAction(CommandGeneratorAction, CommandAction):
790
797
803
805 c = env.get(self.var, '')
806 gen_cmd = apply(Action, (c,)+self.gen_args, self.gen_kw)
807 if not gen_cmd:
808 raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c)))
809 return gen_cmd
810
811 - def _generate(self, target, source, env, for_signature):
813
814 - def __call__(self, target, source, env, *args, **kw):
818
819 - def get_contents(self, target, source, env):
820 c = self.get_parent_class(env)
821 return c.get_contents(self, target, source, env)
822
823
824
826 """Class for Python function actions."""
827
829 if __debug__: logInstanceCreation(self, 'Action.FunctionAction')
830
831 if not cmdstr is _null:
832 if callable(cmdstr):
833 args = (cmdstr,)+args
834 elif not (cmdstr is None or SCons.Util.is_String(cmdstr)):
835 raise SCons.Errors.UserError(\
836 'Invalid function display variable type. ' \
837 'You must either pass a string or a callback which ' \
838 'accepts (target, source, env) as parameters.')
839
840 self.execfunction = execfunction
841 try:
842 self.funccontents = _callable_contents(execfunction)
843 except AttributeError:
844 try:
845
846 self.gc = execfunction.get_contents
847 except AttributeError:
848
849 self.funccontents = _object_contents(execfunction)
850
851 apply(_ActionAction.__init__, (self,)+args, kw)
852 self.varlist = kw.get('varlist', [])
853 if SCons.Util.is_String(self.varlist):
854
855 self.varlist=[self.varlist]
856 self.cmdstr = cmdstr
857
859 try:
860 return self.execfunction.__name__
861 except AttributeError:
862 try:
863 return self.execfunction.__class__.__name__
864 except AttributeError:
865 return "unknown_python_function"
866
884 return '[' + string.join(map(quote, a), ", ") + ']'
885 try:
886 strfunc = self.execfunction.strfunction
887 except AttributeError:
888 pass
889 else:
890 if strfunc is None:
891 return None
892 if callable(strfunc):
893 return strfunc(target, source, env)
894 name = self.function_name()
895 tstr = array(target)
896 sstr = array(source)
897 return "%s(%s, %s)" % (name, tstr, sstr)
898
900 name = self.function_name()
901 if name == 'ActionCaller':
902 return str(self.execfunction)
903 return "%s(target, source, env)" % name
904
905 - def execute(self, target, source, env):
906 rsources = map(rfile, source)
907 try:
908 result = self.execfunction(target=target, source=rsources, env=env)
909 except EnvironmentError, e:
910
911
912
913
914
915 try: filename = e.filename
916 except AttributeError: filename = None
917 result = SCons.Errors.BuildError(node=target,
918 errstr=e.strerror,
919 status=1,
920 filename=filename,
921 action=self,
922 command=self.strfunction(target, source, env))
923 else:
924 if result:
925 msg = "Error %s" % result
926 result = SCons.Errors.BuildError(errstr=msg,
927 status=result,
928 action=self,
929 command=self.strfunction(target, source, env))
930 return result
931
932 - def get_contents(self, target, source, env):
933 """Return the signature contents of this callable action."""
934 try:
935 contents = self.gc(target, source, env)
936 except AttributeError:
937 contents = self.funccontents
938
939 result = [contents]
940 for v in self.varlist:
941 result.append(env.subst('${'+v+'}'))
942
943 return string.join(result, '')
944
947
949 """Class for lists of other actions."""
956 self.list = map(list_of_actions, list)
957
959 return string.join(map(lambda a, t=target, s=source, e=env:
960 a.genstring(t, s, e),
961 self.list),
962 '\n')
963
965 return string.join(map(str, self.list), '\n')
966
970
971 - def get_contents(self, target, source, env):
972 """Return the signature contents of this action list.
973
974 Simple concatenation of the signatures of the elements.
975 """
976 return string.join(map(lambda x, t=target, s=source, e=env:
977 x.get_contents(t, s, e),
978 self.list),
979 "")
980
983 for act in self.list:
984 stat = act(target, source, env, exitstatfunc, presub,
985 show, execute, chdir)
986 if stat:
987 return stat
988 return 0
989
995
997 """A class for delaying calling an Action function with specific
998 (positional and keyword) arguments until the Action is actually
999 executed.
1000
1001 This class looks to the rest of the world like a normal Action object,
1002 but what it's really doing is hanging on to the arguments until we
1003 have a target, source and env to use for the expansion.
1004 """
1006 self.parent = parent
1007 self.args = args
1008 self.kw = kw
1009 - def get_contents(self, target, source, env):
1010 actfunc = self.parent.actfunc
1011 try:
1012
1013 contents = str(actfunc.func_code.co_code)
1014 except AttributeError:
1015
1016 try:
1017 contents = str(actfunc.__call__.im_func.func_code.co_code)
1018 except AttributeError:
1019
1020
1021 contents = str(actfunc)
1022 contents = remove_set_lineno_codes(contents)
1023 return contents
1024 - def subst(self, s, target, source, env):
1042 return map(lambda x, self=self, t=target, s=source, e=env:
1043 self.subst(x, t, s, e),
1044 self.args)
1045 - def subst_kw(self, target, source, env):
1046 kw = {}
1047 for key in self.kw.keys():
1048 kw[key] = self.subst(self.kw[key], target, source, env)
1049 return kw
1050 - def __call__(self, target, source, env):
1051 args = self.subst_args(target, source, env)
1052 kw = self.subst_kw(target, source, env)
1053 return apply(self.parent.actfunc, args, kw)
1055 args = self.subst_args(target, source, env)
1056 kw = self.subst_kw(target, source, env)
1057 return apply(self.parent.strfunc, args, kw)
1059 return apply(self.parent.strfunc, self.args, self.kw)
1060
1062 """A factory class that will wrap up an arbitrary function
1063 as an SCons-executable Action object.
1064
1065 The real heavy lifting here is done by the ActionCaller class.
1066 We just collect the (positional and keyword) arguments that we're
1067 called with and give them to the ActionCaller object we create,
1068 so it can hang onto them until it needs them.
1069 """
1070 - def __init__(self, actfunc, strfunc, convert=lambda x: x):
1071 self.actfunc = actfunc
1072 self.strfunc = strfunc
1073 self.convert = convert
1078