403 line = line[:-1] |
407 line = line[:-1] |
404 |
408 |
405 ## printerr(line) ##debug |
409 ## printerr(line) ##debug |
406 |
410 |
407 if "jsonrpc" in line: |
411 if "jsonrpc" in line: |
408 return self.__handleJsonCommand(line) |
412 return self.handleJsonCommand(line) |
409 |
413 |
410 eoc = line.find('<') |
414 eoc = line.find('<') |
411 |
415 |
412 if eoc >= 0 and line[0] == '>': |
416 if eoc >= 0 and line[0] == '>': |
413 # Get the command part and any argument. |
417 # Get the command part and any argument. |
414 cmd = line[:eoc + 1] |
418 cmd = line[:eoc + 1] |
415 arg = line[eoc + 1:] |
419 arg = line[eoc + 1:] |
416 |
420 |
417 if cmd == DebugProtocol.RequestVariables: |
421 ## if cmd == DebugProtocol.RequestVariables: |
418 frmnr, scope, filter = eval(arg.replace("u'", "'")) |
422 ## frmnr, scope, filter = eval(arg.replace("u'", "'")) |
419 self.__dumpVariables(int(frmnr), int(scope), filter) |
423 ## self.__dumpVariables(int(frmnr), int(scope), filter) |
420 return |
424 ## return |
421 |
425 ## |
422 if cmd == DebugProtocol.RequestVariable: |
426 ## if cmd == DebugProtocol.RequestVariable: |
423 var, frmnr, scope, filter = eval(arg.replace("u'", "'")) |
427 ## var, frmnr, scope, filter = eval(arg.replace("u'", "'")) |
424 self.__dumpVariable(var, int(frmnr), int(scope), filter) |
428 ## self.__dumpVariable(var, int(frmnr), int(scope), filter) |
425 return |
429 ## return |
426 |
430 ## |
427 if cmd == DebugProtocol.RequestThreadList: |
431 ## if cmd == DebugProtocol.RequestThreadList: |
428 self.__dumpThreadList() |
432 ## self.__dumpThreadList() |
429 return |
433 ## return |
430 |
434 ## |
431 if cmd == DebugProtocol.RequestThreadSet: |
435 ## if cmd == DebugProtocol.RequestThreadSet: |
432 tid = eval(arg) |
436 ## tid = eval(arg) |
433 if tid in self.threads: |
437 ## if tid in self.threads: |
434 self.setCurrentThread(tid) |
438 ## self.setCurrentThread(tid) |
435 self.write(DebugProtocol.ResponseThreadSet + '\n') |
439 ## self.write(DebugProtocol.ResponseThreadSet + '\n') |
436 stack = self.currentThread.getStack() |
440 ## stack = self.currentThread.getStack() |
437 self.write('{0}{1!r}\n'.format( |
441 ## self.write('{0}{1!r}\n'.format( |
438 DebugProtocol.ResponseStack, stack)) |
442 ## DebugProtocol.ResponseStack, stack)) |
439 return |
443 ## return |
440 |
444 ## |
441 ## if cmd == DebugProtocol.RequestStep: |
445 ## if cmd == DebugProtocol.RequestStep: |
442 ## self.currentThread.step(True) |
446 ## self.currentThread.step(True) |
443 ## self.eventExit = True |
447 ## self.eventExit = True |
444 ## return |
448 ## return |
445 ## |
449 ## |
674 ## self.cover.stop() |
678 ## self.cover.stop() |
675 ## self.cover.save() |
679 ## self.cover.save() |
676 ## self.writestream.flush() |
680 ## self.writestream.flush() |
677 ## self.progTerminated(res) |
681 ## self.progTerminated(res) |
678 ## return |
682 ## return |
679 |
683 ## |
680 if cmd == DebugProtocol.RequestShutdown: |
684 ## if cmd == DebugProtocol.RequestShutdown: |
681 self.sessionClose() |
685 ## self.sessionClose() |
682 return |
686 ## return |
683 |
687 ## |
684 if cmd == DebugProtocol.RequestBreak: |
688 ## if cmd == DebugProtocol.RequestBreak: |
685 fn, line, temporary, set, cond = arg.split('@@') |
689 ## fn, line, temporary, set, cond = arg.split('@@') |
686 line = int(line) |
690 ## line = int(line) |
687 set = int(set) |
691 ## set = int(set) |
688 temporary = int(temporary) |
692 ## temporary = int(temporary) |
689 |
693 ## |
690 if set: |
694 ## if set: |
691 if cond == 'None' or cond == '': |
695 ## if cond == 'None' or cond == '': |
692 cond = None |
696 ## cond = None |
693 else: |
697 ## else: |
694 try: |
698 ## try: |
695 compile(cond, '<string>', 'eval') |
699 ## compile(cond, '<string>', 'eval') |
696 except SyntaxError: |
700 ## except SyntaxError: |
697 self.write('{0}{1},{2:d}\n'.format( |
701 ## self.write('{0}{1},{2:d}\n'.format( |
698 DebugProtocol.ResponseBPConditionError, |
702 ## DebugProtocol.ResponseBPConditionError, |
699 fn, line)) |
703 ## fn, line)) |
700 return |
704 ## return |
701 self.mainThread.set_break(fn, line, temporary, cond) |
705 ## self.mainThread.set_break(fn, line, temporary, cond) |
702 else: |
706 ## else: |
703 self.mainThread.clear_break(fn, line) |
707 ## self.mainThread.clear_break(fn, line) |
704 |
708 ## |
705 return |
709 ## return |
706 |
710 ## |
707 if cmd == DebugProtocol.RequestBreakEnable: |
711 ## if cmd == DebugProtocol.RequestBreakEnable: |
708 fn, line, enable = arg.split(',') |
712 ## fn, line, enable = arg.split(',') |
709 line = int(line) |
713 ## line = int(line) |
710 enable = int(enable) |
714 ## enable = int(enable) |
711 |
715 ## |
712 bp = self.mainThread.get_break(fn, line) |
716 ## bp = self.mainThread.get_break(fn, line) |
713 if bp is not None: |
717 ## if bp is not None: |
714 if enable: |
718 ## if enable: |
715 bp.enable() |
719 ## bp.enable() |
716 else: |
720 ## else: |
717 bp.disable() |
721 ## bp.disable() |
718 |
722 ## |
719 return |
723 ## return |
720 |
724 ## |
721 if cmd == DebugProtocol.RequestBreakIgnore: |
725 ## if cmd == DebugProtocol.RequestBreakIgnore: |
722 fn, line, count = arg.split(',') |
726 ## fn, line, count = arg.split(',') |
723 line = int(line) |
727 ## line = int(line) |
724 count = int(count) |
728 ## count = int(count) |
725 |
729 ## |
726 bp = self.mainThread.get_break(fn, line) |
730 ## bp = self.mainThread.get_break(fn, line) |
727 if bp is not None: |
731 ## if bp is not None: |
728 bp.ignore = count |
732 ## bp.ignore = count |
729 |
733 ## |
730 return |
734 ## return |
731 |
735 ## |
732 if cmd == DebugProtocol.RequestWatch: |
736 ## if cmd == DebugProtocol.RequestWatch: |
733 cond, temporary, set = arg.split('@@') |
737 ## cond, temporary, set = arg.split('@@') |
734 set = int(set) |
738 ## set = int(set) |
735 temporary = int(temporary) |
739 ## temporary = int(temporary) |
736 |
740 ## |
737 if set: |
741 ## if set: |
738 if not cond.endswith('??created??') and \ |
742 ## if not cond.endswith('??created??') and \ |
739 not cond.endswith('??changed??'): |
743 ## not cond.endswith('??changed??'): |
740 try: |
744 ## try: |
741 compile(cond, '<string>', 'eval') |
745 ## compile(cond, '<string>', 'eval') |
742 except SyntaxError: |
746 ## except SyntaxError: |
743 self.write('{0}{1}\n'.format( |
747 ## self.write('{0}{1}\n'.format( |
744 DebugProtocol.ResponseWPConditionError, cond)) |
748 ## DebugProtocol.ResponseWPConditionError, cond)) |
745 return |
749 ## return |
746 self.mainThread.set_watch(cond, temporary) |
750 ## self.mainThread.set_watch(cond, temporary) |
747 else: |
751 ## else: |
748 self.mainThread.clear_watch(cond) |
752 ## self.mainThread.clear_watch(cond) |
749 |
753 ## |
750 return |
754 ## return |
751 |
755 ## |
752 if cmd == DebugProtocol.RequestWatchEnable: |
756 ## if cmd == DebugProtocol.RequestWatchEnable: |
753 cond, enable = arg.split(',') |
757 ## cond, enable = arg.split(',') |
754 enable = int(enable) |
758 ## enable = int(enable) |
755 |
759 ## |
756 bp = self.mainThread.get_watch(cond) |
760 ## bp = self.mainThread.get_watch(cond) |
757 if bp is not None: |
761 ## if bp is not None: |
758 if enable: |
762 ## if enable: |
759 bp.enable() |
763 ## bp.enable() |
760 else: |
764 ## else: |
761 bp.disable() |
765 ## bp.disable() |
762 |
766 ## |
763 return |
767 ## return |
764 |
768 ## |
765 if cmd == DebugProtocol.RequestWatchIgnore: |
769 ## if cmd == DebugProtocol.RequestWatchIgnore: |
766 cond, count = arg.split(',') |
770 ## cond, count = arg.split(',') |
767 count = int(count) |
771 ## count = int(count) |
768 |
772 ## |
769 bp = self.mainThread.get_watch(cond) |
773 ## bp = self.mainThread.get_watch(cond) |
770 if bp is not None: |
774 ## if bp is not None: |
771 bp.ignore = count |
775 ## bp.ignore = count |
772 |
776 ## |
773 return |
777 ## return |
774 |
778 ## |
775 if cmd == DebugProtocol.RequestEval: |
779 ## if cmd == DebugProtocol.RequestEval: |
776 try: |
780 ## try: |
777 value = eval( |
781 ## value = eval( |
778 arg, |
782 ## arg, |
779 self.currentThread.getCurrentFrame().f_globals, |
783 ## self.currentThread.getCurrentFrame().f_globals, |
780 self.currentThread.getFrameLocals(self.framenr)) |
784 ## self.currentThread.getFrameLocals(self.framenr)) |
781 self.currentThread.storeFrameLocals(self.framenr) |
785 ## self.currentThread.storeFrameLocals(self.framenr) |
782 except Exception: |
786 ## except Exception: |
783 # Report the exception and the traceback |
787 ## # Report the exception and the traceback |
784 try: |
788 ## try: |
785 type, value, tb = sys.exc_info() |
789 ## type, value, tb = sys.exc_info() |
786 sys.last_type = type |
790 ## sys.last_type = type |
787 sys.last_value = value |
791 ## sys.last_value = value |
788 sys.last_traceback = tb |
792 ## sys.last_traceback = tb |
789 tblist = traceback.extract_tb(tb) |
793 ## tblist = traceback.extract_tb(tb) |
790 del tblist[:1] |
794 ## del tblist[:1] |
791 list = traceback.format_list(tblist) |
795 ## list = traceback.format_list(tblist) |
792 if list: |
796 ## if list: |
793 list.insert(0, "Traceback (innermost last):\n") |
797 ## list.insert(0, "Traceback (innermost last):\n") |
794 list[len(list):] = \ |
798 ## list[len(list):] = \ |
795 traceback.format_exception_only(type, value) |
799 ## traceback.format_exception_only(type, value) |
796 finally: |
800 ## finally: |
797 tblist = tb = None |
801 ## tblist = tb = None |
798 |
802 ## |
799 for l in list: |
803 ## for l in list: |
800 self.write(l) |
804 ## self.write(l) |
801 |
805 ## |
802 self.write(DebugProtocol.ResponseException + '\n') |
806 ## self.write(DebugProtocol.ResponseException + '\n') |
803 |
807 ## |
804 else: |
808 ## else: |
805 self.write(str(value) + '\n') |
809 ## self.write(str(value) + '\n') |
806 self.write(DebugProtocol.ResponseOK + '\n') |
810 ## self.write(DebugProtocol.ResponseOK + '\n') |
807 |
811 ## |
808 return |
812 ## return |
809 |
813 ## |
810 if cmd == DebugProtocol.RequestExec: |
814 ## if cmd == DebugProtocol.RequestExec: |
811 _globals = self.currentThread.getCurrentFrame().f_globals |
815 ## _globals = self.currentThread.getCurrentFrame().f_globals |
812 _locals = self.currentThread.getFrameLocals(self.framenr) |
816 ## _locals = self.currentThread.getFrameLocals(self.framenr) |
813 try: |
817 ## try: |
814 code = compile(arg + '\n', '<stdin>', 'single') |
818 ## code = compile(arg + '\n', '<stdin>', 'single') |
815 exec(code, _globals, _locals) |
819 ## exec(code, _globals, _locals) |
816 self.currentThread.storeFrameLocals(self.framenr) |
820 ## self.currentThread.storeFrameLocals(self.framenr) |
817 except Exception: |
821 ## except Exception: |
818 # Report the exception and the traceback |
822 ## # Report the exception and the traceback |
819 try: |
823 ## try: |
820 type, value, tb = sys.exc_info() |
824 ## type, value, tb = sys.exc_info() |
821 sys.last_type = type |
825 ## sys.last_type = type |
822 sys.last_value = value |
826 ## sys.last_value = value |
823 sys.last_traceback = tb |
827 ## sys.last_traceback = tb |
824 tblist = traceback.extract_tb(tb) |
828 ## tblist = traceback.extract_tb(tb) |
825 del tblist[:1] |
829 ## del tblist[:1] |
826 list = traceback.format_list(tblist) |
830 ## list = traceback.format_list(tblist) |
827 if list: |
831 ## if list: |
828 list.insert(0, "Traceback (innermost last):\n") |
832 ## list.insert(0, "Traceback (innermost last):\n") |
829 list[len(list):] = \ |
833 ## list[len(list):] = \ |
830 traceback.format_exception_only(type, value) |
834 ## traceback.format_exception_only(type, value) |
831 finally: |
835 ## finally: |
832 tblist = tb = None |
836 ## tblist = tb = None |
833 |
837 ## |
834 for l in list: |
838 ## for l in list: |
835 self.write(l) |
839 ## self.write(l) |
836 |
840 ## |
837 self.write(DebugProtocol.ResponseException + '\n') |
841 ## self.write(DebugProtocol.ResponseException + '\n') |
838 |
842 ## |
839 return |
843 ## return |
840 |
844 ## |
841 ## if cmd == DebugProtocol.RequestBanner: |
845 ## if cmd == DebugProtocol.RequestBanner: |
842 ## self.write('{0}{1}\n'.format(DebugProtocol.ResponseBanner, |
846 ## self.write('{0}{1}\n'.format(DebugProtocol.ResponseBanner, |
843 ## str(("Python {0}".format(sys.version), |
847 ## str(("Python {0}".format(sys.version), |
844 ## socket.gethostname(), self.variant)))) |
848 ## socket.gethostname(), self.variant)))) |
845 ## return |
849 ## return |
848 ## self.write('{0}{1:d}, "Python3"\n'.format( |
852 ## self.write('{0}{1:d}, "Python3"\n'.format( |
849 ## DebugProtocol.ResponseCapabilities, |
853 ## DebugProtocol.ResponseCapabilities, |
850 ## self.__clientCapabilities())) |
854 ## self.__clientCapabilities())) |
851 ## return |
855 ## return |
852 ## |
856 ## |
853 if cmd == DebugProtocol.RequestCompletion: |
857 ## if cmd == DebugProtocol.RequestCompletion: |
854 self.__completionList(arg.replace("u'", "'")) |
858 ## self.__completionList(arg.replace("u'", "'")) |
855 return |
859 ## return |
856 |
860 ## |
857 if cmd == DebugProtocol.RequestSetFilter: |
861 ## if cmd == DebugProtocol.RequestSetFilter: |
858 scope, filterString = eval(arg.replace("u'", "'")) |
862 ## scope, filterString = eval(arg.replace("u'", "'")) |
859 self.__generateFilterObjects(int(scope), filterString) |
863 ## self.__generateFilterObjects(int(scope), filterString) |
860 return |
864 ## return |
861 |
865 ## |
862 if cmd == DebugProtocol.RequestUTPrepare: |
866 if cmd == DebugProtocol.RequestUTPrepare: |
863 fn, tn, tfn, failed, cov, covname, erase = arg.split('|') |
867 fn, tn, tfn, failed, cov, covname, erase = arg.split('|') |
864 sys.path.insert(0, os.path.dirname(os.path.abspath(fn))) |
868 sys.path.insert(0, os.path.dirname(os.path.abspath(fn))) |
865 os.chdir(sys.path[0]) |
869 os.chdir(sys.path[0]) |
866 failed = eval(failed) |
870 failed = eval(failed) |
1365 self.currentThread.go(params["special"]) |
1406 self.currentThread.go(params["special"]) |
1366 self.eventExit = True |
1407 self.eventExit = True |
1367 return |
1408 return |
1368 |
1409 |
1369 if method == "RawInput": |
1410 if method == "RawInput": |
1370 # If we are handling raw mode input then reset the mode and break out |
1411 # If we are handling raw mode input then break out of the current |
1371 # of the current event loop. |
1412 # event loop. |
1372 ## if self.inRawMode: |
|
1373 ## self.inRawMode = False |
|
1374 self.rawLine = params["input"] |
1413 self.rawLine = params["input"] |
1375 self.eventExit = True |
1414 self.eventExit = True |
1376 return |
1415 return |
1377 |
1416 |
|
1417 if method == "RequestBreakpoint": |
|
1418 if params["setBreakpoint"]: |
|
1419 if params["condition"] in ['None', '']: |
|
1420 params["condition"] = None |
|
1421 elif params["condition"] is not None: |
|
1422 try: |
|
1423 compile(params["condition"], '<string>', 'eval') |
|
1424 except SyntaxError: |
|
1425 self.__sendJsonCommand("ResponseBPConditionError", { |
|
1426 "filename": params["filename"], |
|
1427 "line": params["line"], |
|
1428 }) |
|
1429 return |
|
1430 self.mainThread.set_break( |
|
1431 params["filename"], params["line"], params["temporary"], |
|
1432 params["condition"]) |
|
1433 else: |
|
1434 self.mainThread.clear_break(params["filename"], params["line"]) |
|
1435 |
|
1436 return |
|
1437 |
|
1438 if method == "RequestBreakpointEnable": |
|
1439 bp = self.mainThread.get_break(params["filename"], params["line"]) |
|
1440 if bp is not None: |
|
1441 if params["enable"]: |
|
1442 bp.enable() |
|
1443 else: |
|
1444 bp.disable() |
|
1445 return |
|
1446 |
|
1447 if method == "RequestBreakpointIgnore": |
|
1448 bp = self.mainThread.get_break(params["filename"], params["line"]) |
|
1449 if bp is not None: |
|
1450 bp.ignore = params["count"] |
|
1451 return |
|
1452 |
|
1453 if method == "RequestWatch": |
|
1454 if params["setWatch"]: |
|
1455 if not params["condition"].endswith( |
|
1456 ('??created??', '??changed??')): |
|
1457 try: |
|
1458 compile(params["condition"], '<string>', 'eval') |
|
1459 except SyntaxError: |
|
1460 self.__sendJsonCommand("ResponseWatchConditionError", { |
|
1461 "condition": params["condition"], |
|
1462 }) |
|
1463 return |
|
1464 self.mainThread.set_watch( |
|
1465 params["condition"], params["temporary"]) |
|
1466 else: |
|
1467 self.mainThread.clear_watch(params["condition"]) |
|
1468 return |
|
1469 |
|
1470 if method == "RequestWatchEnable": |
|
1471 wp = self.mainThread.get_watch(params["condition"]) |
|
1472 if wp is not None: |
|
1473 if params["enable"]: |
|
1474 wp.enable() |
|
1475 else: |
|
1476 wp.disable() |
|
1477 return |
|
1478 |
|
1479 if method == "RequestWatchIgnore": |
|
1480 wp = self.mainThread.get_watch(params["condition"]) |
|
1481 if wp is not None: |
|
1482 wp.ignore = params["count"] |
|
1483 return |
|
1484 |
|
1485 if method == "RequestShutdown": |
|
1486 self.sessionClose() |
|
1487 return |
|
1488 |
|
1489 if method == "RequestCompletion": |
|
1490 self.__completionList(params["text"]) |
|
1491 return |
1378 |
1492 |
1379 def __sendJsonCommand(self, command, params): |
1493 def __sendJsonCommand(self, command, params): |
1380 """ |
1494 """ |
1381 Private method to send a single command to the client. |
1495 Private method to send a single command to the client. |
1382 |
1496 |
1392 "method": command, |
1506 "method": command, |
1393 "params": params, |
1507 "params": params, |
1394 } |
1508 } |
1395 cmd = json.dumps(commandDict) + '\n' |
1509 cmd = json.dumps(commandDict) + '\n' |
1396 self.write(cmd) |
1510 self.write(cmd) |
|
1511 |
|
1512 def sendClearTemporaryBreakpoint(self, filename, lineno): |
|
1513 """ |
|
1514 Public method to signal the deletion of a temporary breakpoint. |
|
1515 |
|
1516 @param filename name of the file the bp belongs to |
|
1517 @type str |
|
1518 @param lineno linenumber of the bp |
|
1519 @type int |
|
1520 """ |
|
1521 self.__sendJsonCommand("ResponseClearBreakpoint", { |
|
1522 "filename": filename, |
|
1523 "line": lineno |
|
1524 }) |
|
1525 |
|
1526 def sendClearTemporaryWatch(self, condition): |
|
1527 """ |
|
1528 Public method to signal the deletion of a temporary watch expression. |
|
1529 |
|
1530 @param condition condition of the watch expression to be cleared |
|
1531 @type str |
|
1532 """ |
|
1533 self.__sendJsonCommand("ResponseClearWatch", { |
|
1534 "condition": condition, |
|
1535 }) |
|
1536 |
|
1537 def sendResponseLine(self, stack): |
|
1538 """ |
|
1539 Public method to send the current call stack. |
|
1540 |
|
1541 @param stack call stack |
|
1542 @type list |
|
1543 """ |
|
1544 self.__sendJsonCommand("ResponseLine", { |
|
1545 "stack": stack, |
|
1546 }) |
|
1547 |
|
1548 def sendCallTrace(self, event, fromStr, toStr): |
|
1549 """ |
|
1550 Public method to send a call trace entry. |
|
1551 |
|
1552 @param event trace event (call or return) |
|
1553 @type str |
|
1554 @param fromstr pre-formatted origin info |
|
1555 @type str |
|
1556 @param toStr pre-formatted target info |
|
1557 @type str |
|
1558 """ |
|
1559 self.__sendJsonCommand("CallTrace", { |
|
1560 "event": event[0], |
|
1561 "from": fromStr, |
|
1562 "to": toStr, |
|
1563 }) |
|
1564 |
|
1565 def sendException(self, exceptionType, exceptionMessage, stack): |
|
1566 """ |
|
1567 Public method to send information for an exception. |
|
1568 |
|
1569 @param exceptionType type of exception raised |
|
1570 @type str |
|
1571 @param exceptionMessage message of the exception |
|
1572 @type str |
|
1573 @param stack stack trace information |
|
1574 @param list |
|
1575 """ |
|
1576 self.__sendJsonCommand("ResponseException", { |
|
1577 "type": exceptionType, |
|
1578 "message": exceptionMessage, |
|
1579 "stack": stack, |
|
1580 }) |
|
1581 |
|
1582 def sendSyntaxError(self, message, filename, lineno, charno): |
|
1583 """ |
|
1584 Public method to send information for a syntax error. |
|
1585 |
|
1586 @param message syntax error message |
|
1587 @type str |
|
1588 @param filename name of the faulty file |
|
1589 @type str |
|
1590 @param lineno line number info |
|
1591 @type int |
|
1592 @param charno character number info |
|
1593 @tyoe int |
|
1594 """ |
|
1595 self.__sendJsonCommand("ResponseSyntax", { |
|
1596 "message": message, |
|
1597 "filename": filename, |
|
1598 "linenumber": lineno, |
|
1599 "characternumber": charno, |
|
1600 }) |
|
1601 |
|
1602 def sendPassiveStartup(self, filename, exceptions): |
|
1603 """ |
|
1604 Public method to send the passive start information. |
|
1605 |
|
1606 @param filename name of the script |
|
1607 @type str |
|
1608 @param exceptions flag to enable exception reporting of the IDE |
|
1609 @type bool |
|
1610 """ |
|
1611 self.__sendJsonCommand("PassiveStartup", { |
|
1612 "filename": filename, |
|
1613 "exceptions": exceptions, |
|
1614 }) |
1397 |
1615 |
1398 def __clientCapabilities(self): |
1616 def __clientCapabilities(self): |
1399 """ |
1617 """ |
1400 Private method to determine the clients capabilities. |
1618 Private method to determine the clients capabilities. |
1401 |
1619 |