@@ -953,16 +953,16 @@ def do_help(self, args, nl_re=nl_re, indent_re=indent_re):
953953 return 0
954954
955955 def do_history (self , args ):
956- '' """Usage: history designator [skipquiet]
956+ '' """Usage: history designator [skipquiet] [raw]
957957 Show the history entries of a designator.
958958
959959 A designator is a classname and a nodeid concatenated,
960960 eg. bug1, user10, ...
961961
962- Lists the journal entries viewable by the user for the
963- node identified by the designator. If skipquiet is the
964- second argument, journal entries for quiet properties
965- are not shown .
962+ Lists the journal entries viewable by the user for the node
963+ identified by the designator. If skipquiet is added, journal
964+ entries for quiet properties are not shown. If raw is added,
965+ the output is the raw representation of the journal entries .
966966 """
967967
968968 if len (args ) < 1 :
@@ -972,15 +972,181 @@ def do_history(self, args):
972972 except hyperdb .DesignatorError as message :
973973 raise UsageError (message )
974974
975- skipquiet = False
976- if len (args ) == 2 :
977- if args [1 ] != 'skipquiet' :
978- raise UsageError ("Second argument is not skipquiet" )
979- skipquiet = True
975+ valid_args = ['skipquiet' , 'raw' ]
976+
977+ if len (args ) >= 2 :
978+ check = [a for a in args [1 :] if a not in valid_args ]
979+ if check :
980+ raise UsageError (
981+ _ ("Unexpected argument(s): %s. "
982+ "Expected 'skipquiet' or 'raw'." ) % ", " .join (check ))
983+
984+ skipquiet = 'skipquiet' in args [1 :]
985+ raw = 'raw' in args [1 :]
986+
987+ getclass = self .db .getclass
988+ def get_prop_name (key , prop_name ):
989+ # getclass and classname from enclosing method
990+ klass = getclass (classname )
991+ try :
992+ property_obj = klass .properties [prop_name ]
993+ except KeyError :
994+ # the property has been removed from the schema.
995+ return None
996+ if isinstance (property_obj ,
997+ (hyperdb .Link , hyperdb .Multilink )):
998+ prop_class = getclass (property_obj .classname )
999+ key_prop_name = prop_class .key
1000+ if key_prop_name :
1001+ return prop_class .get (key , key_prop_name )
1002+ # None indicates that there is no key_prop
1003+ return None
1004+ return None
1005+
1006+ def get_prop_class (prop_name ):
1007+ # getclass and classname from enclosing method
1008+ klass = getclass (classname )
1009+ try :
1010+ property_obj = klass .properties [prop_name ]
1011+ except KeyError :
1012+ # the property has been removed from the schema.
1013+ return None
1014+ if isinstance (property_obj ,
1015+ (hyperdb .Link , hyperdb .Multilink )):
1016+ prop_class = getclass (property_obj .classname )
1017+ return prop_class .classname
1018+ return None # it's not a link
1019+
1020+ def _format_tuple_change (data , prop ):
1021+ ''' ('-', ['2', '4'] ->
1022+ "removed fred(2), jim(6)"
1023+ '''
1024+ if data [0 ] == '-' :
1025+ op = _ ("removed" )
1026+ elif data [0 ] == '+' :
1027+ op = _ ("added" )
1028+ else :
1029+ raise ValueError (_ ("Unknown history set operation '%s'. "
1030+ "Expected +/-." ) % op )
1031+ op_params = data [1 ]
1032+ name = get_prop_name (op_params [0 ], prop )
1033+ if name is not None :
1034+ list_items = ["%s(%s)" %
1035+ (get_prop_name (o , prop ), o )
1036+ for o in op_params ]
1037+ else :
1038+ propclass = get_prop_class (prop )
1039+ if propclass : # noqa: SIM108
1040+ list_items = ["%s%s" % (propclass , o )
1041+ for o in op_params ]
1042+ else :
1043+ list_items = op_params
1044+
1045+ return "%s: %s" % (op , ", " .join (list_items ))
1046+
1047+ def format_report_class (_data ):
1048+ """Eat the empty data dictionary or None"""
1049+ return classname
1050+
1051+ def format_link (data ):
1052+ '''data = ('issue', '157', 'dependson')'''
1053+ # .Hint added issue23 to superseder
1054+ f = _ ("added %(class)s%(item_id)s to %(propname)s" )
1055+ return f % {
1056+ 'class' : data [0 ], 'item_id' : data [1 ], 'propname' : data [2 ]}
1057+
1058+ def format_set (data ):
1059+ '''data = set {'fyi': None, 'priority': '5'}
1060+ set {'fyi': '....\n ed through cleanly', 'priority': '3'}
1061+ '''
1062+ result = []
1063+
1064+ # Note that set data is the old value. So don't use
1065+ # current/future tense in sentences.
1066+
1067+ for prop , value in data .items ():
1068+ if isinstance (value , str ):
1069+ name = get_prop_name (value , prop )
1070+ if name :
1071+ result .append (
1072+ # .Hint read as: assignedto was admin(1)
1073+ # .Hint where assignedto is the property
1074+ # .Hint admin is the key name for value 1
1075+ _ ("%(prop)s was %(name)%(value)s)" ) % {
1076+ "prop" : prop , "name" : name , "value" : value })
1077+ else :
1078+ # use repr so strings with embedded \n etc. don't
1079+ # generate newlines in output. Try to keep each
1080+ # journal entry on 1 line.
1081+ result .append (_ ("%(prop)s was %(value)s" ) % {
1082+ "prop" : prop , "value" : repr (value )})
1083+ elif isinstance (value , list ):
1084+ # test to see if there is a key prop.
1085+ # Assumption, geting None here means no key
1086+ # is defined for the property's class.
1087+ name = get_prop_name (value [0 ], prop )
1088+ if name is not None :
1089+ list_items = ["%s(%s)" %
1090+ (get_prop_name (v , prop ), v )
1091+ for v in value ]
1092+ else :
1093+ prop_class = get_prop_class (prop )
1094+ if prop_class : # noqa: SIM108
1095+ list_items = [ "%s%s" % (prop_class , v )
1096+ for v in value ]
1097+ else :
1098+ list_items = value
1099+
1100+ result .append (_ ("%(prop)s was [%(value_list)s]" ) % {
1101+ "prop" : prop , "value_list" : ", " .join (list_items )})
1102+ elif isinstance (value , tuple ):
1103+ # operation data
1104+ decorated = [_format_tuple_change (data , prop )
1105+ for data in value ]
1106+ result .append (# .Hint modified nosy: added demo(3)
1107+ _ ("modified %(prop)s: %(how)s" ) % {
1108+ "prop" : prop , "how" : ", " .join (decorated )})
1109+ else :
1110+ result .append (_ ("%(prop)s was %(value)s" ) % {
1111+ "prop" : prop , "value" : value })
1112+
1113+ return '; ' .join (result )
1114+
1115+ def format_unlink (data ):
1116+ '''data = ('issue', '157', 'dependson')'''
1117+ return "removed %s%s from %s" % (data [0 ], data [1 ], data [2 ])
1118+
1119+ formatters = {
1120+ "create" : format_report_class ,
1121+ "link" : format_link ,
1122+ "restored" : format_report_class ,
1123+ "retired" : format_report_class ,
1124+ "set" : format_set ,
1125+ "unlink" : format_unlink ,
1126+ }
9801127
9811128 try :
982- print (self .db .getclass (classname ).history (nodeid ,
983- skipquiet = skipquiet ))
1129+ # returns a tuple: (
1130+ # [0] = nodeid
1131+ # [1] = date
1132+ # [2] = userid
1133+ # [3] = operation
1134+ # [4] = details
1135+ raw_history = self .db .getclass (classname ).history (nodeid ,
1136+ skipquiet = skipquiet )
1137+ if raw :
1138+ print (raw_history )
1139+ return 0
1140+
1141+ def make_readable (hist ):
1142+ return "%s(%s) %s %s" % (self .db .user .get (hist [2 ], 'username' ),
1143+ hist [1 ],
1144+ hist [3 ],
1145+ formatters .get (hist [3 ], lambda x : x )(
1146+ hist [4 ]))
1147+ printable_history = [make_readable (hist ) for hist in raw_history ]
1148+
1149+ print ("\n " .join (printable_history ))
9841150 except KeyError :
9851151 raise UsageError (_ ('no such class "%(classname)s"' ) % locals ())
9861152 except IndexError :
@@ -2103,7 +2269,7 @@ def interactive(self):
21032269 ".roundup_admin_history" )
21042270
21052271 try :
2106- import readline # noqa: F401
2272+ import readline
21072273 readline .read_init_file (initfile )
21082274 try :
21092275 readline .read_history_file (histfile )
@@ -2168,10 +2334,10 @@ def main(self): # noqa: PLR0912, PLR0911
21682334 self .print_designator = 0
21692335 self .verbose = 0
21702336 for opt , arg in opts :
2171- if opt == '-h' : # noqa: RET505 - allow elif after returns
2337+ if opt == '-h' :
21722338 self .usage ()
21732339 return 0
2174- elif opt == '-v' :
2340+ elif opt == '-v' : # noqa: RET505 - allow elif after returns
21752341 print ('%s (python %s)' % (roundup_version ,
21762342 sys .version .split ()[0 ]))
21772343 return 0
0 commit comments