diff options
Diffstat (limited to 'contrib/python')
| -rw-r--r-- | contrib/python/pg8000/.dist-info/METADATA | 181 | ||||
| -rw-r--r-- | contrib/python/pg8000/README.md | 177 | ||||
| -rw-r--r-- | contrib/python/pg8000/pg8000/converters.py | 2 | ||||
| -rw-r--r-- | contrib/python/pg8000/pg8000/core.py | 2 | ||||
| -rw-r--r-- | contrib/python/pg8000/ya.make | 2 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/.dist-info/METADATA | 9 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/__init__.py | 32 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/actions.py | 168 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/common.py | 143 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/core.py | 1251 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/diagram/__init__.py | 44 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/exceptions.py | 33 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/helpers.py | 587 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/results.py | 474 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/testing.py | 96 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/pyparsing/util.py | 48 | ||||
| -rw-r--r-- | contrib/python/pyparsing/py3/ya.make | 2 |
17 files changed, 2194 insertions, 1057 deletions
diff --git a/contrib/python/pg8000/.dist-info/METADATA b/contrib/python/pg8000/.dist-info/METADATA index b8e7e656413..2378ba7a3f5 100644 --- a/contrib/python/pg8000/.dist-info/METADATA +++ b/contrib/python/pg8000/.dist-info/METADATA @@ -1,8 +1,8 @@ Metadata-Version: 2.4 Name: pg8000 -Version: 1.31.4 +Version: 1.31.5 Summary: PostgreSQL interface library -Project-URL: Homepage, https://github.com/tlocke/pg8000 +Project-URL: Homepage, https://codeberg.org/tlocke/pg8000 Author: The Contributors License: BSD 3-Clause License License-File: LICENSE @@ -36,10 +36,172 @@ pg8000 is a pure-[Python](https://www.python.org/) belief that it is probably about the 8000th PostgreSQL interface for Python. pg8000 is distributed under the BSD 3-clause license. -All bug reports, feature requests and contributions are welcome at -[http://github.com/tlocke/pg8000/](http://github.com/tlocke/pg8000/). +<!-- mtoc-start --> -[](https://github.com/tlocke/pg8000/actions/workflows/test.yml) +* [Installation](#installation) +* [Native API Interactive Examples](#native-api-interactive-examples) + * [Basic Example](#basic-example) + * [Transactions](#transactions) + * [Query Using Functions](#query-using-functions) + * [Interval Type](#interval-type) + * [Point Type](#point-type) + * [Client Encoding](#client-encoding) + * [JSON](#json) + * [Retrieve Column Metadata From Results](#retrieve-column-metadata-from-results) + * [Notices And Notifications](#notices-and-notifications) + * [Parameter Statuses](#parameter-statuses) + * [LIMIT ALL](#limit-all) + * [IN and NOT IN](#in-and-not-in) + * [Many SQL Statements Can't Be Parameterized](#many-sql-statements-cant-be-parameterized) + * [COPY FROM And TO A Stream](#copy-from-and-to-a-stream) + * [Execute Multiple SQL Statements](#execute-multiple-sql-statements) + * [Quoted Identifiers in SQL](#quoted-identifiers-in-sql) + * [Custom adapter from a Python type to a PostgreSQL type](#custom-adapter-from-a-python-type-to-a-postgresql-type) + * [Custom adapter from a PostgreSQL type to a Python type](#custom-adapter-from-a-postgresql-type-to-a-python-type) + * [Could Not Determine Data Type Of Parameter](#could-not-determine-data-type-of-parameter) + * [Prepared Statements](#prepared-statements) + * [Use Environment Variables As Connection Defaults](#use-environment-variables-as-connection-defaults) + * [Connect To PostgreSQL Over SSL](#connect-to-postgresql-over-ssl) + * [Server-Side Cursors](#server-side-cursors) + * [BLOBs (Binary Large Objects)](#blobs-binary-large-objects) + * [Replication Protocol](#replication-protocol) + * [Extra Startup Parameters](#extra-startup-parameters) + * [PostgreSQL-Style Parameter Placeholders](#postgresql-style-parameter-placeholders) +* [DB-API 2 Interactive Examples](#db-api-2-interactive-examples) + * [Basic Example](#basic-example-1) + * [Query Using Functions](#query-using-functions-1) + * [Interval Type](#interval-type-1) + * [Point Type](#point-type-1) + * [Numeric Parameter Style](#numeric-parameter-style) + * [Autocommit](#autocommit) + * [Client Encoding](#client-encoding-1) + * [JSON](#json-1) + * [Retrieve Column Names From Results](#retrieve-column-names-from-results) + * [COPY from and to a file](#copy-from-and-to-a-file) + * [Server-Side Cursors](#server-side-cursors-1) + * [BLOBs (Binary Large Objects)](#blobs-binary-large-objects-1) + * [Parameter Limit](#parameter-limit) +* [Type Mapping](#type-mapping) +* [Theory Of Operation](#theory-of-operation) +* [Native API Docs](#native-api-docs) + * [pg8000.native.Error](#pg8000nativeerror) + * [pg8000.native.InterfaceError](#pg8000nativeinterfaceerror) + * [pg8000.native.DatabaseError](#pg8000nativedatabaseerror) + * [pg8000.native.Connection(user, host='localhost', database=None, port=5432, password=None, source\_address=None, unix\_sock=None, ssl\_context=None, timeout=None, tcp\_keepalive=True, application\_name=None, replication=None, sock=None)](#pg8000nativeconnectionuser-hostlocalhost-databasenone-port5432-passwordnone-source_addressnone-unix_socknone-ssl_contextnone-timeoutnone-tcp_keepalivetrue-application_namenone-replicationnone-socknone) + * [pg8000.native.Connection.notifications](#pg8000nativeconnectionnotifications) + * [pg8000.native.Connection.notices](#pg8000nativeconnectionnotices) + * [pg8000.native.Connection.parameter\_statuses](#pg8000nativeconnectionparameter_statuses) + * [pg8000.native.Connection.run(sql, stream=None, types=None, \*\*kwargs)](#pg8000nativeconnectionrunsql-streamnone-typesnone-kwargs) + * [pg8000.native.Connection.row\_count](#pg8000nativeconnectionrow_count) + * [pg8000.native.Connection.columns](#pg8000nativeconnectioncolumns) + * [pg8000.native.Connection.close()](#pg8000nativeconnectionclose) + * [pg8000.native.Connection.register\_out\_adapter(typ, out\_func)](#pg8000nativeconnectionregister_out_adaptertyp-out_func) + * [pg8000.native.Connection.register\_in\_adapter(oid, in\_func)](#pg8000nativeconnectionregister_in_adapteroid-in_func) + * [pg8000.native.Connection.prepare(sql)](#pg8000nativeconnectionpreparesql) + * [pg8000.native.PreparedStatement](#pg8000nativepreparedstatement) + * [pg8000.native.PreparedStatement.run(\*\*kwargs)](#pg8000nativepreparedstatementrunkwargs) + * [pg8000.native.PreparedStatement.close()](#pg8000nativepreparedstatementclose) + * [pg8000.native.identifier(ident)](#pg8000nativeidentifierident) + * [pg8000.native.literal(value)](#pg8000nativeliteralvalue) +* [DB-API 2 Docs](#db-api-2-docs) + * [Properties](#properties) + * [pg8000.dbapi.apilevel](#pg8000dbapiapilevel) + * [pg8000.dbapi.threadsafety](#pg8000dbapithreadsafety) + * [pg8000.dbapi.paramstyle](#pg8000dbapiparamstyle) + * [pg8000.dbapi.STRING](#pg8000dbapistring) + * [pg8000.dbapi.BINARY](#pg8000dbapibinary) + * [pg8000.dbapi.NUMBER](#pg8000dbapinumber) + * [pg8000.dbapi.DATETIME](#pg8000dbapidatetime) + * [pg8000.dbapi.ROWID](#pg8000dbapirowid) + * [Functions](#functions) + * [pg8000.dbapi.connect(user, host='localhost', database=None, port=5432, password=None, source\_address=None, unix\_sock=None, ssl\_context=None, timeout=None, tcp\_keepalive=True, applicationa_name=None, replication=None, sock=None)](#pg8000dbapiconnectuser-hostlocalhost-databasenone-port5432-passwordnone-source_addressnone-unix_socknone-ssl_contextnone-timeoutnone-tcp_keepalivetrue-applicationa_namenone-replicationnone-socknone) + * [pg8000.dbapi.Date(year, month, day)](#pg8000dbapidateyear-month-day) + * [pg8000.dbapi.Time(hour, minute, second)](#pg8000dbapitimehour-minute-second) + * [pg8000.dbapi.Timestamp(year, month, day, hour, minute, second)](#pg8000dbapitimestampyear-month-day-hour-minute-second) + * [pg8000.dbapi.DateFromTicks(ticks)](#pg8000dbapidatefromticksticks) + * [pg8000.dbapi.TimeFromTicks(ticks)](#pg8000dbapitimefromticksticks) + * [pg8000.dbapi.TimestampFromTicks(ticks)](#pg8000dbapitimestampfromticksticks) + * [pg8000.dbapi.Binary(value)](#pg8000dbapibinaryvalue) + * [Generic Exceptions](#generic-exceptions) + * [pg8000.dbapi.Warning](#pg8000dbapiwarning) + * [pg8000.dbapi.Error](#pg8000dbapierror) + * [pg8000.dbapi.InterfaceError](#pg8000dbapiinterfaceerror) + * [pg8000.dbapi.DatabaseError](#pg8000dbapidatabaseerror) + * [pg8000.dbapi.DataError](#pg8000dbapidataerror) + * [pg8000.dbapi.OperationalError](#pg8000dbapioperationalerror) + * [pg8000.dbapi.IntegrityError](#pg8000dbapiintegrityerror) + * [pg8000.dbapi.InternalError](#pg8000dbapiinternalerror) + * [pg8000.dbapi.ProgrammingError](#pg8000dbapiprogrammingerror) + * [pg8000.dbapi.NotSupportedError](#pg8000dbapinotsupportederror) + * [Classes](#classes) + * [pg8000.dbapi.Connection](#pg8000dbapiconnection) + * [pg8000.dbapi.Connection.autocommit](#pg8000dbapiconnectionautocommit) + * [pg8000.dbapi.Connection.close()](#pg8000dbapiconnectionclose) + * [pg8000.dbapi.Connection.cursor()](#pg8000dbapiconnectioncursor) + * [pg8000.dbapi.Connection.rollback()](#pg8000dbapiconnectionrollback) + * [pg8000.dbapi.Connection.tpc\_begin(xid)](#pg8000dbapiconnectiontpc_beginxid) + * [pg8000.dbapi.Connection.tpc_commit(xid=None)](#pg8000dbapiconnectiontpc_commitxidnone) + * [pg8000.dbapi.Connection.tpc_prepare()](#pg8000dbapiconnectiontpc_prepare) + * [pg8000.dbapi.Connection.tpc_recover()](#pg8000dbapiconnectiontpc_recover) + * [pg8000.dbapi.Connection.tpc_rollback(xid=None)](#pg8000dbapiconnectiontpc_rollbackxidnone) + * [pg8000.dbapi.Connection.xid(format_id, global_transaction_id, branch_qualifier)](#pg8000dbapiconnectionxidformat_id-global_transaction_id-branch_qualifier) + * [pg8000.dbapi.Cursor](#pg8000dbapicursor) + * [pg8000.dbapi.Cursor.arraysize](#pg8000dbapicursorarraysize) + * [pg8000.dbapi.Cursor.connection](#pg8000dbapicursorconnection) + * [pg8000.dbapi.Cursor.rowcount](#pg8000dbapicursorrowcount) + * [pg8000.dbapi.Cursor.description](#pg8000dbapicursordescription) + * [pg8000.dbapi.Cursor.close()](#pg8000dbapicursorclose) + * [pg8000.dbapi.Cursor.execute(operation, args=None, stream=None)](#pg8000dbapicursorexecuteoperation-argsnone-streamnone) + * [pg8000.dbapi.Cursor.executemany(operation, param_sets)](#pg8000dbapicursorexecutemanyoperation-param_sets) + * [pg8000.dbapi.Cursor.callproc(procname, parameters=None)](#pg8000dbapicursorcallprocprocname-parametersnone) + * [pg8000.dbapi.Cursor.fetchall()](#pg8000dbapicursorfetchall) + * [pg8000.dbapi.Cursor.fetchmany(size=None)](#pg8000dbapicursorfetchmanysizenone) + * [pg8000.dbapi.Cursor.fetchone()](#pg8000dbapicursorfetchone) + * [pg8000.dbapi.Cursor.setinputsizes(\*sizes)](#pg8000dbapicursorsetinputsizessizes) + * [pg8000.dbapi.Cursor.setoutputsize(size, column=None)](#pg8000dbapicursorsetoutputsizesize-columnnone) + * [pg8000.dbapi.Interval](#pg8000dbapiinterval) +* [Design Decisions](#design-decisions) +* [Tests](#tests) +* [Doing A Release Of pg8000](#doing-a-release-of-pg8000) +* [Release Notes](#release-notes) + * [Version 1.31.5, 2025-09-14](#version-1315-2025-09-14) + * [Version 1.31.4, 2025-07-20](#version-1314-2025-07-20) + * [Version 1.31.3, 2025-07-19](#version-1313-2025-07-19) + * [Version 1.31.2, 2024-04-28](#version-1312-2024-04-28) + * [Version 1.31.1, 2024-04-01](#version-1311-2024-04-01) + * [Version 1.31.0, 2024-03-31](#version-1310-2024-03-31) + * [Version 1.30.5, 2024-02-22](#version-1305-2024-02-22) + * [Version 1.30.4, 2024-01-03](#version-1304-2024-01-03) + * [Version 1.30.3, 2023-10-31](#version-1303-2023-10-31) + * [Version 1.30.2, 2023-09-17](#version-1302-2023-09-17) + * [Version 1.30.1, 2023-07-29](#version-1301-2023-07-29) + * [Version 1.30.0, 2023-07-27](#version-1300-2023-07-27) + * [Version 1.29.8, 2023-06-16](#version-1298-2023-06-16) + * [Version 1.29.7, 2023-06-16](#version-1297-2023-06-16) + * [Version 1.29.6, 2023-05-29](#version-1296-2023-05-29) + * [Version 1.29.5, 2023-05-09](#version-1295-2023-05-09) + * [Version 1.29.4, 2022-12-14](#version-1294-2022-12-14) + * [Version 1.29.3, 2022-10-26](#version-1293-2022-10-26) + * [Version 1.29.2, 2022-10-09](#version-1292-2022-10-09) + * [Version 1.29.1, 2022-05-23](#version-1291-2022-05-23) + * [Version 1.29.0, 2022-05-21](#version-1290-2022-05-21) + * [Version 1.28.3, 2022-05-18](#version-1283-2022-05-18) + * [Version 1.28.2, 2022-05-17](#version-1282-2022-05-17) + * [Version 1.28.1, 2022-05-17](#version-1281-2022-05-17) + * [Version 1.28.0, 2022-05-17](#version-1280-2022-05-17) + * [Version 1.27.1, 2022-05-16](#version-1271-2022-05-16) + * [Version 1.27.0, 2022-05-16](#version-1270-2022-05-16) + * [Version 1.26.1, 2022-04-23](#version-1261-2022-04-23) + * [Version 1.26.0, 2022-04-18](#version-1260-2022-04-18) + * [Version 1.25.0, 2022-04-17](#version-1250-2022-04-17) + * [Version 1.24.2, 2022-04-15](#version-1242-2022-04-15) + * [Version 1.24.1, 2022-03-02](#version-1241-2022-03-02) + * [Version 1.24.0, 2022-02-06](#version-1240-2022-02-06) + * [Version 1.23.0, 2021-11-13](#version-1230-2021-11-13) + * [Version 1.22.1, 2021-11-10](#version-1221-2021-11-10) + * [Version 1.22.0, 2021-10-13](#version-1220-2021-10-13) + +<!-- mtoc-end --> ## Installation @@ -140,7 +302,7 @@ rolling back a transaction: ``` -NB. There is [a longstanding bug](https://github.com/tlocke/pg8000/issues/36>) in the +NB. There is [a longstanding bug](https://codeberg.org/tlocke/pg8000/issues/36) in the PostgreSQL server whereby if a `COMMIT` is issued against a failed transaction, the transaction is silently rolled back, rather than an error being returned. pg8000 attempts to detect when this has happened and raise an `InterfaceError`. @@ -2137,6 +2299,13 @@ twine upload dist/* ## Release Notes +### Version 1.31.5, 2025-09-14 + +- Tiny performance improvement in reading from socket. +- Fix bug in `literal()` where list is not properly escaped. +- Move to [Codeberg](https://codeberg.org/tlocke/pg8000). + + ### Version 1.31.4, 2025-07-20 - Various speed optimisations. diff --git a/contrib/python/pg8000/README.md b/contrib/python/pg8000/README.md index d351b68c4ea..28abf15cb65 100644 --- a/contrib/python/pg8000/README.md +++ b/contrib/python/pg8000/README.md @@ -6,10 +6,172 @@ pg8000 is a pure-[Python](https://www.python.org/) belief that it is probably about the 8000th PostgreSQL interface for Python. pg8000 is distributed under the BSD 3-clause license. -All bug reports, feature requests and contributions are welcome at -[http://github.com/tlocke/pg8000/](http://github.com/tlocke/pg8000/). +<!-- mtoc-start --> -[](https://github.com/tlocke/pg8000/actions/workflows/test.yml) +* [Installation](#installation) +* [Native API Interactive Examples](#native-api-interactive-examples) + * [Basic Example](#basic-example) + * [Transactions](#transactions) + * [Query Using Functions](#query-using-functions) + * [Interval Type](#interval-type) + * [Point Type](#point-type) + * [Client Encoding](#client-encoding) + * [JSON](#json) + * [Retrieve Column Metadata From Results](#retrieve-column-metadata-from-results) + * [Notices And Notifications](#notices-and-notifications) + * [Parameter Statuses](#parameter-statuses) + * [LIMIT ALL](#limit-all) + * [IN and NOT IN](#in-and-not-in) + * [Many SQL Statements Can't Be Parameterized](#many-sql-statements-cant-be-parameterized) + * [COPY FROM And TO A Stream](#copy-from-and-to-a-stream) + * [Execute Multiple SQL Statements](#execute-multiple-sql-statements) + * [Quoted Identifiers in SQL](#quoted-identifiers-in-sql) + * [Custom adapter from a Python type to a PostgreSQL type](#custom-adapter-from-a-python-type-to-a-postgresql-type) + * [Custom adapter from a PostgreSQL type to a Python type](#custom-adapter-from-a-postgresql-type-to-a-python-type) + * [Could Not Determine Data Type Of Parameter](#could-not-determine-data-type-of-parameter) + * [Prepared Statements](#prepared-statements) + * [Use Environment Variables As Connection Defaults](#use-environment-variables-as-connection-defaults) + * [Connect To PostgreSQL Over SSL](#connect-to-postgresql-over-ssl) + * [Server-Side Cursors](#server-side-cursors) + * [BLOBs (Binary Large Objects)](#blobs-binary-large-objects) + * [Replication Protocol](#replication-protocol) + * [Extra Startup Parameters](#extra-startup-parameters) + * [PostgreSQL-Style Parameter Placeholders](#postgresql-style-parameter-placeholders) +* [DB-API 2 Interactive Examples](#db-api-2-interactive-examples) + * [Basic Example](#basic-example-1) + * [Query Using Functions](#query-using-functions-1) + * [Interval Type](#interval-type-1) + * [Point Type](#point-type-1) + * [Numeric Parameter Style](#numeric-parameter-style) + * [Autocommit](#autocommit) + * [Client Encoding](#client-encoding-1) + * [JSON](#json-1) + * [Retrieve Column Names From Results](#retrieve-column-names-from-results) + * [COPY from and to a file](#copy-from-and-to-a-file) + * [Server-Side Cursors](#server-side-cursors-1) + * [BLOBs (Binary Large Objects)](#blobs-binary-large-objects-1) + * [Parameter Limit](#parameter-limit) +* [Type Mapping](#type-mapping) +* [Theory Of Operation](#theory-of-operation) +* [Native API Docs](#native-api-docs) + * [pg8000.native.Error](#pg8000nativeerror) + * [pg8000.native.InterfaceError](#pg8000nativeinterfaceerror) + * [pg8000.native.DatabaseError](#pg8000nativedatabaseerror) + * [pg8000.native.Connection(user, host='localhost', database=None, port=5432, password=None, source\_address=None, unix\_sock=None, ssl\_context=None, timeout=None, tcp\_keepalive=True, application\_name=None, replication=None, sock=None)](#pg8000nativeconnectionuser-hostlocalhost-databasenone-port5432-passwordnone-source_addressnone-unix_socknone-ssl_contextnone-timeoutnone-tcp_keepalivetrue-application_namenone-replicationnone-socknone) + * [pg8000.native.Connection.notifications](#pg8000nativeconnectionnotifications) + * [pg8000.native.Connection.notices](#pg8000nativeconnectionnotices) + * [pg8000.native.Connection.parameter\_statuses](#pg8000nativeconnectionparameter_statuses) + * [pg8000.native.Connection.run(sql, stream=None, types=None, \*\*kwargs)](#pg8000nativeconnectionrunsql-streamnone-typesnone-kwargs) + * [pg8000.native.Connection.row\_count](#pg8000nativeconnectionrow_count) + * [pg8000.native.Connection.columns](#pg8000nativeconnectioncolumns) + * [pg8000.native.Connection.close()](#pg8000nativeconnectionclose) + * [pg8000.native.Connection.register\_out\_adapter(typ, out\_func)](#pg8000nativeconnectionregister_out_adaptertyp-out_func) + * [pg8000.native.Connection.register\_in\_adapter(oid, in\_func)](#pg8000nativeconnectionregister_in_adapteroid-in_func) + * [pg8000.native.Connection.prepare(sql)](#pg8000nativeconnectionpreparesql) + * [pg8000.native.PreparedStatement](#pg8000nativepreparedstatement) + * [pg8000.native.PreparedStatement.run(\*\*kwargs)](#pg8000nativepreparedstatementrunkwargs) + * [pg8000.native.PreparedStatement.close()](#pg8000nativepreparedstatementclose) + * [pg8000.native.identifier(ident)](#pg8000nativeidentifierident) + * [pg8000.native.literal(value)](#pg8000nativeliteralvalue) +* [DB-API 2 Docs](#db-api-2-docs) + * [Properties](#properties) + * [pg8000.dbapi.apilevel](#pg8000dbapiapilevel) + * [pg8000.dbapi.threadsafety](#pg8000dbapithreadsafety) + * [pg8000.dbapi.paramstyle](#pg8000dbapiparamstyle) + * [pg8000.dbapi.STRING](#pg8000dbapistring) + * [pg8000.dbapi.BINARY](#pg8000dbapibinary) + * [pg8000.dbapi.NUMBER](#pg8000dbapinumber) + * [pg8000.dbapi.DATETIME](#pg8000dbapidatetime) + * [pg8000.dbapi.ROWID](#pg8000dbapirowid) + * [Functions](#functions) + * [pg8000.dbapi.connect(user, host='localhost', database=None, port=5432, password=None, source\_address=None, unix\_sock=None, ssl\_context=None, timeout=None, tcp\_keepalive=True, applicationa_name=None, replication=None, sock=None)](#pg8000dbapiconnectuser-hostlocalhost-databasenone-port5432-passwordnone-source_addressnone-unix_socknone-ssl_contextnone-timeoutnone-tcp_keepalivetrue-applicationa_namenone-replicationnone-socknone) + * [pg8000.dbapi.Date(year, month, day)](#pg8000dbapidateyear-month-day) + * [pg8000.dbapi.Time(hour, minute, second)](#pg8000dbapitimehour-minute-second) + * [pg8000.dbapi.Timestamp(year, month, day, hour, minute, second)](#pg8000dbapitimestampyear-month-day-hour-minute-second) + * [pg8000.dbapi.DateFromTicks(ticks)](#pg8000dbapidatefromticksticks) + * [pg8000.dbapi.TimeFromTicks(ticks)](#pg8000dbapitimefromticksticks) + * [pg8000.dbapi.TimestampFromTicks(ticks)](#pg8000dbapitimestampfromticksticks) + * [pg8000.dbapi.Binary(value)](#pg8000dbapibinaryvalue) + * [Generic Exceptions](#generic-exceptions) + * [pg8000.dbapi.Warning](#pg8000dbapiwarning) + * [pg8000.dbapi.Error](#pg8000dbapierror) + * [pg8000.dbapi.InterfaceError](#pg8000dbapiinterfaceerror) + * [pg8000.dbapi.DatabaseError](#pg8000dbapidatabaseerror) + * [pg8000.dbapi.DataError](#pg8000dbapidataerror) + * [pg8000.dbapi.OperationalError](#pg8000dbapioperationalerror) + * [pg8000.dbapi.IntegrityError](#pg8000dbapiintegrityerror) + * [pg8000.dbapi.InternalError](#pg8000dbapiinternalerror) + * [pg8000.dbapi.ProgrammingError](#pg8000dbapiprogrammingerror) + * [pg8000.dbapi.NotSupportedError](#pg8000dbapinotsupportederror) + * [Classes](#classes) + * [pg8000.dbapi.Connection](#pg8000dbapiconnection) + * [pg8000.dbapi.Connection.autocommit](#pg8000dbapiconnectionautocommit) + * [pg8000.dbapi.Connection.close()](#pg8000dbapiconnectionclose) + * [pg8000.dbapi.Connection.cursor()](#pg8000dbapiconnectioncursor) + * [pg8000.dbapi.Connection.rollback()](#pg8000dbapiconnectionrollback) + * [pg8000.dbapi.Connection.tpc\_begin(xid)](#pg8000dbapiconnectiontpc_beginxid) + * [pg8000.dbapi.Connection.tpc_commit(xid=None)](#pg8000dbapiconnectiontpc_commitxidnone) + * [pg8000.dbapi.Connection.tpc_prepare()](#pg8000dbapiconnectiontpc_prepare) + * [pg8000.dbapi.Connection.tpc_recover()](#pg8000dbapiconnectiontpc_recover) + * [pg8000.dbapi.Connection.tpc_rollback(xid=None)](#pg8000dbapiconnectiontpc_rollbackxidnone) + * [pg8000.dbapi.Connection.xid(format_id, global_transaction_id, branch_qualifier)](#pg8000dbapiconnectionxidformat_id-global_transaction_id-branch_qualifier) + * [pg8000.dbapi.Cursor](#pg8000dbapicursor) + * [pg8000.dbapi.Cursor.arraysize](#pg8000dbapicursorarraysize) + * [pg8000.dbapi.Cursor.connection](#pg8000dbapicursorconnection) + * [pg8000.dbapi.Cursor.rowcount](#pg8000dbapicursorrowcount) + * [pg8000.dbapi.Cursor.description](#pg8000dbapicursordescription) + * [pg8000.dbapi.Cursor.close()](#pg8000dbapicursorclose) + * [pg8000.dbapi.Cursor.execute(operation, args=None, stream=None)](#pg8000dbapicursorexecuteoperation-argsnone-streamnone) + * [pg8000.dbapi.Cursor.executemany(operation, param_sets)](#pg8000dbapicursorexecutemanyoperation-param_sets) + * [pg8000.dbapi.Cursor.callproc(procname, parameters=None)](#pg8000dbapicursorcallprocprocname-parametersnone) + * [pg8000.dbapi.Cursor.fetchall()](#pg8000dbapicursorfetchall) + * [pg8000.dbapi.Cursor.fetchmany(size=None)](#pg8000dbapicursorfetchmanysizenone) + * [pg8000.dbapi.Cursor.fetchone()](#pg8000dbapicursorfetchone) + * [pg8000.dbapi.Cursor.setinputsizes(\*sizes)](#pg8000dbapicursorsetinputsizessizes) + * [pg8000.dbapi.Cursor.setoutputsize(size, column=None)](#pg8000dbapicursorsetoutputsizesize-columnnone) + * [pg8000.dbapi.Interval](#pg8000dbapiinterval) +* [Design Decisions](#design-decisions) +* [Tests](#tests) +* [Doing A Release Of pg8000](#doing-a-release-of-pg8000) +* [Release Notes](#release-notes) + * [Version 1.31.5, 2025-09-14](#version-1315-2025-09-14) + * [Version 1.31.4, 2025-07-20](#version-1314-2025-07-20) + * [Version 1.31.3, 2025-07-19](#version-1313-2025-07-19) + * [Version 1.31.2, 2024-04-28](#version-1312-2024-04-28) + * [Version 1.31.1, 2024-04-01](#version-1311-2024-04-01) + * [Version 1.31.0, 2024-03-31](#version-1310-2024-03-31) + * [Version 1.30.5, 2024-02-22](#version-1305-2024-02-22) + * [Version 1.30.4, 2024-01-03](#version-1304-2024-01-03) + * [Version 1.30.3, 2023-10-31](#version-1303-2023-10-31) + * [Version 1.30.2, 2023-09-17](#version-1302-2023-09-17) + * [Version 1.30.1, 2023-07-29](#version-1301-2023-07-29) + * [Version 1.30.0, 2023-07-27](#version-1300-2023-07-27) + * [Version 1.29.8, 2023-06-16](#version-1298-2023-06-16) + * [Version 1.29.7, 2023-06-16](#version-1297-2023-06-16) + * [Version 1.29.6, 2023-05-29](#version-1296-2023-05-29) + * [Version 1.29.5, 2023-05-09](#version-1295-2023-05-09) + * [Version 1.29.4, 2022-12-14](#version-1294-2022-12-14) + * [Version 1.29.3, 2022-10-26](#version-1293-2022-10-26) + * [Version 1.29.2, 2022-10-09](#version-1292-2022-10-09) + * [Version 1.29.1, 2022-05-23](#version-1291-2022-05-23) + * [Version 1.29.0, 2022-05-21](#version-1290-2022-05-21) + * [Version 1.28.3, 2022-05-18](#version-1283-2022-05-18) + * [Version 1.28.2, 2022-05-17](#version-1282-2022-05-17) + * [Version 1.28.1, 2022-05-17](#version-1281-2022-05-17) + * [Version 1.28.0, 2022-05-17](#version-1280-2022-05-17) + * [Version 1.27.1, 2022-05-16](#version-1271-2022-05-16) + * [Version 1.27.0, 2022-05-16](#version-1270-2022-05-16) + * [Version 1.26.1, 2022-04-23](#version-1261-2022-04-23) + * [Version 1.26.0, 2022-04-18](#version-1260-2022-04-18) + * [Version 1.25.0, 2022-04-17](#version-1250-2022-04-17) + * [Version 1.24.2, 2022-04-15](#version-1242-2022-04-15) + * [Version 1.24.1, 2022-03-02](#version-1241-2022-03-02) + * [Version 1.24.0, 2022-02-06](#version-1240-2022-02-06) + * [Version 1.23.0, 2021-11-13](#version-1230-2021-11-13) + * [Version 1.22.1, 2021-11-10](#version-1221-2021-11-10) + * [Version 1.22.0, 2021-10-13](#version-1220-2021-10-13) + +<!-- mtoc-end --> ## Installation @@ -110,7 +272,7 @@ rolling back a transaction: ``` -NB. There is [a longstanding bug](https://github.com/tlocke/pg8000/issues/36>) in the +NB. There is [a longstanding bug](https://codeberg.org/tlocke/pg8000/issues/36) in the PostgreSQL server whereby if a `COMMIT` is issued against a failed transaction, the transaction is silently rolled back, rather than an error being returned. pg8000 attempts to detect when this has happened and raise an `InterfaceError`. @@ -2107,6 +2269,13 @@ twine upload dist/* ## Release Notes +### Version 1.31.5, 2025-09-14 + +- Tiny performance improvement in reading from socket. +- Fix bug in `literal()` where list is not properly escaped. +- Move to [Codeberg](https://codeberg.org/tlocke/pg8000). + + ### Version 1.31.4, 2025-07-20 - Various speed optimisations. diff --git a/contrib/python/pg8000/pg8000/converters.py b/contrib/python/pg8000/pg8000/converters.py index 9a3c0937599..fe025a53ac5 100644 --- a/contrib/python/pg8000/pg8000/converters.py +++ b/contrib/python/pg8000/pg8000/converters.py @@ -833,4 +833,4 @@ def _(value: Timedelta): @literal.register def _(value: list): - return f"'{array_out(value)}'" + return f"{literal(array_out(value))}" diff --git a/contrib/python/pg8000/pg8000/core.py b/contrib/python/pg8000/pg8000/core.py index f4f59f22434..b4524806f2e 100644 --- a/contrib/python/pg8000/pg8000/core.py +++ b/contrib/python/pg8000/pg8000/core.py @@ -147,7 +147,7 @@ def _flush(sock): def _read(sock, size): - buff = bytearray() + buff = bytearray(sock.read(size)) try: while len(buff) < size: block = sock.read(size - len(buff)) diff --git a/contrib/python/pg8000/ya.make b/contrib/python/pg8000/ya.make index 949b64fa399..78adf678d54 100644 --- a/contrib/python/pg8000/ya.make +++ b/contrib/python/pg8000/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(1.31.4) +VERSION(1.31.5) LICENSE(BSD-3-Clause) diff --git a/contrib/python/pyparsing/py3/.dist-info/METADATA b/contrib/python/pyparsing/py3/.dist-info/METADATA index d03671b6130..0c6093fe53e 100644 --- a/contrib/python/pyparsing/py3/.dist-info/METADATA +++ b/contrib/python/pyparsing/py3/.dist-info/METADATA @@ -1,14 +1,14 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: pyparsing -Version: 3.2.3 -Summary: pyparsing module - Classes and methods to define and execute parsing grammars +Version: 3.2.5 +Summary: pyparsing - Classes and methods to define and execute parsing grammars Author-email: Paul McGuire <[email protected]> Requires-Python: >=3.9 Description-Content-Type: text/x-rst +License-Expression: MIT Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology -Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 @@ -24,6 +24,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Compilers Classifier: Topic :: Text Processing Classifier: Typing :: Typed +License-File: LICENSE Requires-Dist: railroad-diagrams ; extra == "diagrams" Requires-Dist: jinja2 ; extra == "diagrams" Project-URL: Homepage, https://github.com/pyparsing/pyparsing/ diff --git a/contrib/python/pyparsing/py3/pyparsing/__init__.py b/contrib/python/pyparsing/py3/pyparsing/__init__.py index d839fd25375..502519cdef3 100644 --- a/contrib/python/pyparsing/py3/pyparsing/__init__.py +++ b/contrib/python/pyparsing/py3/pyparsing/__init__.py @@ -23,21 +23,23 @@ # __doc__ = """ -pyparsing module - Classes and methods to define and execute parsing grammars -============================================================================= +pyparsing - Classes and methods to define and execute parsing grammars +====================================================================== -The pyparsing module is an alternative approach to creating and -executing simple grammars, vs. the traditional lex/yacc approach, or the -use of regular expressions. With pyparsing, you don't need to learn -a new syntax for defining grammars or matching expressions - the parsing -module provides a library of classes that you use to construct the -grammar directly in Python. +Pyparsing is an alternative approach to creating and executing simple +grammars, vs. the traditional lex/yacc approach, or the use of regular +expressions. With pyparsing, you don't need to learn a new syntax for +defining grammars or matching expressions - the parsing module provides +a library of classes that you use to construct the grammar directly in +Python. Here is a program to parse "Hello, World!" (or any greeting of the form ``"<salutation>, <addressee>!"``), built up using :class:`Word`, :class:`Literal`, and :class:`And` elements (the :meth:`'+'<ParserElement.__add__>` operators create :class:`And` expressions, -and the strings are auto-converted to :class:`Literal` expressions):: +and the strings are auto-converted to :class:`Literal` expressions): + +.. testcode:: from pyparsing import Word, alphas @@ -47,7 +49,9 @@ and the strings are auto-converted to :class:`Literal` expressions):: hello = "Hello, World!" print(hello, "->", greet.parse_string(hello)) -The program outputs the following:: +The program outputs the following: + +.. testoutput:: Hello, World! -> ['Hello', ',', 'World', '!'] @@ -69,8 +73,8 @@ vexing when writing text parsers: - embedded comments -Getting Started - ------------------ +Getting Started +--------------- Visit the classes :class:`ParserElement` and :class:`ParseResults` to see the base classes that most other pyparsing classes inherit from. Use the docstrings for examples of how to: @@ -120,8 +124,8 @@ class version_info(NamedTuple): return f"{__name__}.{type(self).__name__}({', '.join('{}={!r}'.format(*nv) for nv in zip(self._fields, self))})" -__version_info__ = version_info(3, 2, 3, "final", 1) -__version_time__ = "25 Mar 2025 01:38 UTC" +__version_info__ = version_info(3, 2, 5, "final", 1) +__version_time__ = "16 Sep 2025 22:24 UTC" __version__ = __version_info__.__version__ __versionTime__ = __version_time__ __author__ = "Paul McGuire <[email protected]>" diff --git a/contrib/python/pyparsing/py3/pyparsing/actions.py b/contrib/python/pyparsing/py3/pyparsing/actions.py index 0153cc7132a..0d80d2cf911 100644 --- a/contrib/python/pyparsing/py3/pyparsing/actions.py +++ b/contrib/python/pyparsing/py3/pyparsing/actions.py @@ -56,36 +56,45 @@ def match_only_at_col(n: int) -> ParseAction: return verify_col -def replace_with(repl_str: str) -> ParseAction: +def replace_with(repl_str: Any) -> ParseAction: """ Helper method for common parse actions that simply return a literal value. Especially useful when used with - :class:`transform_string<ParserElement.transform_string>` (). + :meth:`~ParserElement.transform_string`. - Example:: + Example: - num = Word(nums).set_parse_action(lambda toks: int(toks[0])) - na = one_of("N/A NA").set_parse_action(replace_with(math.nan)) - term = na | num + .. doctest:: - term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234] + >>> num = Word(nums).set_parse_action(lambda toks: int(toks[0])) + >>> na = one_of("N/A NA").set_parse_action(replace_with(math.nan)) + >>> term = na | num + + >>> term[1, ...].parse_string("324 234 N/A 234") + ParseResults([324, 234, nan, 234], {}) """ return lambda s, l, t: [repl_str] def remove_quotes(s: str, l: int, t: ParseResults) -> Any: - """ + r""" Helper parse action for removing quotation marks from parsed - quoted strings. + quoted strings, that use a single character for quoting. For parsing + strings that may have multiple characters, use the :class:`QuotedString` + class. + + Example: - Example:: + .. doctest:: - # by default, quotation marks are included in parsed results - quoted_string.parse_string("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"] + >>> # by default, quotation marks are included in parsed results + >>> quoted_string.parse_string("'Now is the Winter of our Discontent'") + ParseResults(["'Now is the Winter of our Discontent'"], {}) - # use remove_quotes to strip quotation marks from parsed results - quoted_string.set_parse_action(remove_quotes) - quoted_string.parse_string("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"] + >>> # use remove_quotes to strip quotation marks from parsed results + >>> dequoted = quoted_string().set_parse_action(remove_quotes) + >>> dequoted.parse_string("'Now is the Winter of our Discontent'") + ParseResults(['Now is the Winter of our Discontent'], {}) """ return t[0][1:-1] @@ -115,36 +124,53 @@ def with_attribute(*args: tuple[str, str], **attr_dict) -> ParseAction: To verify that the attribute exists, but without specifying a value, pass ``with_attribute.ANY_VALUE`` as the value. - Example:: + The next two examples use the following input data and tag parsers: + + .. testcode:: + + html = ''' + <div> + Some text + <div type="grid">1 4 0 1 0</div> + <div type="graph">1,3 2,3 1,1</div> + <div>this has no type</div> + </div> + ''' + div,div_end = make_html_tags("div") + + Only match div tag having a type attribute with value "grid": + + .. testcode:: + + div_grid = div().set_parse_action(with_attribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.search_string(html): + print(grid_header.body) + + prints: - html = ''' - <div> - Some text - <div type="grid">1 4 0 1 0</div> - <div type="graph">1,3 2,3 1,1</div> - <div>this has no type</div> - </div> - ''' - div,div_end = make_html_tags("div") + .. testoutput:: - # only match div tag having a type attribute with value "grid" - div_grid = div().set_parse_action(with_attribute(type="grid")) - grid_expr = div_grid + SkipTo(div | div_end)("body") - for grid_header in grid_expr.search_string(html): - print(grid_header.body) + 1 4 0 1 0 - # construct a match with any div tag having a type attribute, regardless of the value - div_any_type = div().set_parse_action(with_attribute(type=with_attribute.ANY_VALUE)) - div_expr = div_any_type + SkipTo(div | div_end)("body") - for div_header in div_expr.search_string(html): - print(div_header.body) + Construct a match with any div tag having a type attribute, + regardless of the value: - prints:: + .. testcode:: - 1 4 0 1 0 + div_any_type = div().set_parse_action( + with_attribute(type=with_attribute.ANY_VALUE) + ) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.search_string(html): + print(div_header.body) - 1 4 0 1 0 - 1,3 2,3 1,1 + prints: + + .. testoutput:: + + 1 4 0 1 0 + 1,3 2,3 1,1 """ attrs_list: list[tuple[str, str]] = [] if args: @@ -171,39 +197,57 @@ with_attribute.ANY_VALUE = object() # type: ignore [attr-defined] def with_class(classname: str, namespace: str = "") -> ParseAction: """ - Simplified version of :class:`with_attribute` when + Simplified version of :meth:`with_attribute` when matching on a div class - made difficult because ``class`` is a reserved word in Python. - Example:: + Using similar input data to the :meth:`with_attribute` examples: + + .. testcode:: + + html = ''' + <div> + Some text + <div class="grid">1 4 0 1 0</div> + <div class="graph">1,3 2,3 1,1</div> + <div>this <div> has no class</div> + </div> + ''' + div,div_end = make_html_tags("div") + + Only match div tag having the "grid" class: + + .. testcode:: + + div_grid = div().set_parse_action(with_class("grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.search_string(html): + print(grid_header.body) + + prints: + + .. testoutput:: - html = ''' - <div> - Some text - <div class="grid">1 4 0 1 0</div> - <div class="graph">1,3 2,3 1,1</div> - <div>this <div> has no class</div> - </div> + 1 4 0 1 0 - ''' - div,div_end = make_html_tags("div") - div_grid = div().set_parse_action(with_class("grid")) + Construct a match with any div tag having a class attribute, + regardless of the value: - grid_expr = div_grid + SkipTo(div | div_end)("body") - for grid_header in grid_expr.search_string(html): - print(grid_header.body) + .. testcode:: - div_any_type = div().set_parse_action(with_class(withAttribute.ANY_VALUE)) - div_expr = div_any_type + SkipTo(div | div_end)("body") - for div_header in div_expr.search_string(html): - print(div_header.body) + div_any_type = div().set_parse_action( + with_class(withAttribute.ANY_VALUE) + ) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.search_string(html): + print(div_header.body) - prints:: + prints: - 1 4 0 1 0 + .. testoutput:: - 1 4 0 1 0 - 1,3 2,3 1,1 + 1 4 0 1 0 + 1,3 2,3 1,1 """ classattr = f"{namespace}:class" if namespace else "class" return with_attribute(**{classattr: classname}) diff --git a/contrib/python/pyparsing/py3/pyparsing/common.py b/contrib/python/pyparsing/py3/pyparsing/common.py index e46511086da..dbf9ba88e9d 100644 --- a/contrib/python/pyparsing/py3/pyparsing/common.py +++ b/contrib/python/pyparsing/py3/pyparsing/common.py @@ -30,7 +30,9 @@ class pyparsing_common: - :class:`upcase_tokens` - :class:`downcase_tokens` - Example:: + Examples: + + .. testcode:: pyparsing_common.number.run_tests(''' # any int or real number, returned as the appropriate type @@ -42,44 +44,9 @@ class pyparsing_common: 1e-12 ''') - pyparsing_common.fnumber.run_tests(''' - # any int or real number, returned as float - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - ''') - - pyparsing_common.hex_integer.run_tests(''' - # hex numbers - 100 - FF - ''') - - pyparsing_common.fraction.run_tests(''' - # fractions - 1/2 - -3/4 - ''') - - pyparsing_common.mixed_integer.run_tests(''' - # mixed fractions - 1 - 1/2 - -3/4 - 1-3/4 - ''') - - import uuid - pyparsing_common.uuid.set_parse_action(token_map(uuid.UUID)) - pyparsing_common.uuid.run_tests(''' - # uuid - 12345678-1234-5678-1234-567812345678 - ''') + .. testoutput:: + :options: +NORMALIZE_WHITESPACE - prints:: # any int or real number, returned as the appropriate type 100 @@ -100,6 +67,22 @@ class pyparsing_common: 1e-12 [1e-12] + .. testcode:: + + pyparsing_common.fnumber.run_tests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + + # any int or real number, returned as float 100 [100.0] @@ -119,6 +102,18 @@ class pyparsing_common: 1e-12 [1e-12] + .. testcode:: + + pyparsing_common.hex_integer.run_tests(''' + # hex numbers + 100 + FF + ''') + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + + # hex numbers 100 [256] @@ -126,6 +121,18 @@ class pyparsing_common: FF [255] + .. testcode:: + + pyparsing_common.fraction.run_tests(''' + # fractions + 1/2 + -3/4 + ''') + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + + # fractions 1/2 [0.5] @@ -133,6 +140,20 @@ class pyparsing_common: -3/4 [-0.75] + .. testcode:: + + pyparsing_common.mixed_integer.run_tests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + + # mixed fractions 1 [1] @@ -145,6 +166,18 @@ class pyparsing_common: 1-3/4 [1.75] + .. testcode:: + + import uuid + pyparsing_common.uuid.set_parse_action(token_map(uuid.UUID)) + pyparsing_common.uuid.run_tests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + # uuid 12345678-1234-5678-1234-567812345678 @@ -264,13 +297,17 @@ class pyparsing_common: Params - - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%d"``) - Example:: + Example: + + .. testcode:: date_expr = pyparsing_common.iso8601_date.copy() date_expr.set_parse_action(pyparsing_common.convert_to_date()) print(date_expr.parse_string("1999-12-31")) - prints:: + prints: + + .. testoutput:: [datetime.date(1999, 12, 31)] """ @@ -291,13 +328,17 @@ class pyparsing_common: Params - - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%dT%H:%M:%S.%f"``) - Example:: + Example: + + .. testcode:: dt_expr = pyparsing_common.iso8601_datetime.copy() dt_expr.set_parse_action(pyparsing_common.convert_to_datetime()) print(dt_expr.parse_string("1999-12-31T23:59:59.999")) - prints:: + prints: + + .. testoutput:: [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] """ @@ -329,15 +370,20 @@ class pyparsing_common: def strip_html_tags(s: str, l: int, tokens: ParseResults): """Parse action to remove HTML tags from web page HTML source - Example:: + Example: + + .. testcode:: # strip HTML links from normal text text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>' td, td_end = make_html_tags("TD") - table_text = td + SkipTo(td_end).set_parse_action(pyparsing_common.strip_html_tags)("body") + td_end + table_text = td + SkipTo(td_end).set_parse_action( + pyparsing_common.strip_html_tags)("body") + td_end print(table_text.parse_string(text).body) - Prints:: + Prints: + + .. testoutput:: More info at the pyparsing wiki page """ @@ -414,7 +460,12 @@ class pyparsing_common: r"(#(?P<fragment>\S*))?" + r")" ).set_name("url") - """URL (http/https/ftp scheme)""" + """ + URL (http/https/ftp scheme) + + .. versionchanged:: 3.1.0 + ``url`` named group added + """ # fmt: on # pre-PEP8 compatibility names diff --git a/contrib/python/pyparsing/py3/pyparsing/core.py b/contrib/python/pyparsing/py3/pyparsing/core.py index 86be949ad47..9c5894eb42a 100644 --- a/contrib/python/pyparsing/py3/pyparsing/core.py +++ b/contrib/python/pyparsing/py3/pyparsing/core.py @@ -324,14 +324,15 @@ def condition_as_parse_action( """ Function to convert a simple predicate function that returns ``True`` or ``False`` into a parse action. Can be used in places when a parse action is required - and :class:`ParserElement.add_condition` cannot be used (such as when adding a condition + and :meth:`ParserElement.add_condition` cannot be used (such as when adding a condition to an operator level in :class:`infix_notation`). Optional keyword arguments: - - ``message`` - define a custom message to be used in the raised exception - - ``fatal`` - if True, will raise :class:`ParseFatalException` to stop parsing immediately; - otherwise will raise :class:`ParseException` + :param message: define a custom message to be used in the raised exception + :param fatal: if ``True``, will raise :class:`ParseFatalException` + to stop parsing immediately; + otherwise will raise :class:`ParseException` """ msg = message if message is not None else "failed user-defined condition" @@ -398,14 +399,21 @@ class ParserElement(ABC): r""" Overrides the default whitespace chars - Example:: + Example: + + .. doctest:: # default whitespace chars are space, <TAB> and newline - Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl'] + >>> Word(alphas)[1, ...].parse_string("abc def\nghi jkl") + ParseResults(['abc', 'def', 'ghi', 'jkl'], {}) # change to just treat newline as significant - ParserElement.set_default_whitespace_chars(" \t") - Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def'] + >>> ParserElement.set_default_whitespace_chars(" \t") + >>> Word(alphas)[1, ...].parse_string("abc def\nghi jkl") + ParseResults(['abc', 'def'], {}) + + # Reset to default + >>> ParserElement.set_default_whitespace_chars(" \n\t\r") """ ParserElement.DEFAULT_WHITE_CHARS = chars @@ -419,20 +427,37 @@ class ParserElement(ABC): """ Set class to be used for inclusion of string literals into a parser. - Example:: + Example: - # default literal class used is Literal - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + .. doctest:: + :options: +NORMALIZE_WHITESPACE - date_str.parse_string("1999/12/31") # -> ['1999', '/', '12', '/', '31'] + # default literal class used is Literal + >>> integer = Word(nums) + >>> date_str = ( + ... integer("year") + '/' + ... + integer("month") + '/' + ... + integer("day") + ... ) + >>> date_str.parse_string("1999/12/31") + ParseResults(['1999', '/', '12', '/', '31'], + {'year': '1999', 'month': '12', 'day': '31'}) # change to Suppress - ParserElement.inline_literals_using(Suppress) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + >>> ParserElement.inline_literals_using(Suppress) + >>> date_str = ( + ... integer("year") + '/' + ... + integer("month") + '/' + ... + integer("day") + ... ) - date_str.parse_string("1999/12/31") # -> ['1999', '12', '31'] + >>> date_str.parse_string("1999/12/31") + ParseResults(['1999', '12', '31'], + {'year': '1999', 'month': '12', 'day': '31'}) + + # Reset + >>> ParserElement.inline_literals_using(Literal) """ ParserElement._literalStringClass = cls @@ -441,10 +466,13 @@ class ParserElement(ABC): """ Yields a sequence of ``class(obj, **class_kwargs)`` for obj in seq. - Example:: + Example: + + .. testcode:: LPAR, RPAR, LBRACE, RBRACE, SEMI = Suppress.using_each("(){};") + .. versionadded:: 3.1.0 """ yield from (cls(obj, **class_kwargs) for obj in seq) @@ -494,14 +522,20 @@ class ParserElement(ABC): """ Suppress warnings emitted for a particular diagnostic on this expression. - Example:: + Example: - base = pp.Forward() - base.suppress_warning(Diagnostics.warn_on_parse_using_empty_Forward) + .. doctest:: - # statement would normally raise a warning, but is now suppressed - print(base.parse_string("x")) + >>> label = pp.Word(pp.alphas) + # Normally using an empty Forward in a grammar + # would print a warning, but we can suppress that + >>> base = pp.Forward().suppress_warning( + ... pp.Diagnostics.warn_on_parse_using_empty_Forward) + + >>> grammar = base | label + >>> print(grammar.parse_string("x")) + ['x'] """ self.suppress_warnings_.append(warning_type) return self @@ -529,21 +563,34 @@ class ParserElement(ABC): different parse actions for the same parsing pattern, using copies of the original parse element. - Example:: + Example: + + .. testcode:: + + integer = Word(nums).set_parse_action( + lambda toks: int(toks[0])) + integerK = integer.copy().add_parse_action( + lambda toks: toks[0] * 1024) + Suppress("K") + integerM = integer.copy().add_parse_action( + lambda toks: toks[0] * 1024 * 1024) + Suppress("M") - integer = Word(nums).set_parse_action(lambda toks: int(toks[0])) - integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K") - integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") + print( + (integerK | integerM | integer)[1, ...].parse_string( + "5K 100 640K 256M") + ) - print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M")) + prints: - prints:: + .. testoutput:: [5120, 100, 655360, 268435456] - Equivalent form of ``expr.copy()`` is just ``expr()``:: + Equivalent form of ``expr.copy()`` is just ``expr()``: + + .. testcode:: - integerM = integer().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") + integerM = integer().add_parse_action( + lambda toks: toks[0] * 1024 * 1024) + Suppress("M") """ cpy = copy.copy(self) cpy.parseAction = self.parseAction[:] @@ -570,10 +617,12 @@ class ParserElement(ABC): You can also set results names using the abbreviated syntax, ``expr("name")`` in place of ``expr.set_results_name("name")`` - - see :class:`__call__`. If ``list_all_matches`` is required, use + - see :meth:`__call__`. If ``list_all_matches`` is required, use ``expr("name*")``. - Example:: + Example: + + .. testcode:: integer = Word(nums) date_str = (integer.set_results_name("year") + '/' @@ -646,29 +695,39 @@ class ParserElement(ABC): Optional keyword arguments: - - ``call_during_try`` = (default= ``False``) indicate if parse action should be run during - lookaheads and alternate testing. For parse actions that have side effects, it is - important to only call the parse action once it is determined that it is being - called as part of a successful parse. For parse actions that perform additional - validation, then call_during_try should be passed as True, so that the validation - code is included in the preliminary "try" parses. + :param call_during_try: (default= ``False``) indicate if parse action + should be run during lookaheads and alternate + testing. For parse actions that have side + effects, it is important to only call the parse + action once it is determined that it is being + called as part of a successful parse. + For parse actions that perform additional + validation, then ``call_during_try`` should + be passed as True, so that the validation code + is included in the preliminary "try" parses. - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See :class:`parse_string` for more - information on parsing strings containing ``<TAB>`` s, and suggested - methods to maintain a consistent view of the parsed string, the parse - location, and line and column positions within the parsed string. + .. Note:: + The default parsing behavior is to expand tabs in the input string + before starting the parsing process. + See :meth:`parse_string` for more information on parsing strings + containing ``<TAB>`` s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and + line and column positions within the parsed string. - Example:: + Example: Parse dates in the form ``YYYY/MM/DD`` + ----------------------------------------------- - # parse dates in the form YYYY/MM/DD + Setup code: + + .. testcode:: - # use parse action to convert toks from str to int at parse time def convert_to_int(toks): + '''a parse action to convert toks from str to int + at parse time''' return int(toks[0]) - # use a parse action to verify that the date is a valid date def is_valid_date(instring, loc, toks): + '''a parse action to verify that the date is a valid date''' from datetime import date year, month, day = toks[::2] try: @@ -683,14 +742,30 @@ class ParserElement(ABC): integer.set_parse_action(convert_to_int) date_str.set_parse_action(is_valid_date) - # note that integer fields are now ints, not strings - date_str.run_tests(''' - # successful parse - note that integer fields were converted to ints - 1999/12/31 + Successful parse - note that integer fields are converted to ints: - # fail - invalid date - 1999/13/31 - ''') + .. testcode:: + + print(date_str.parse_string("1999/12/31")) + + prints: + + .. testoutput:: + + [1999, '/', 12, '/', 31] + + Failure - invalid date: + + .. testcode:: + + date_str.parse_string("1999/13/31") + + prints: + + .. testoutput:: + + Traceback (most recent call last): + ParseException: invalid date given, found '1999' ... """ if list(fns) == [None]: self.parseAction.clear() @@ -730,15 +805,20 @@ class ParserElement(ABC): - ``call_during_try`` = boolean to indicate if this method should be called during internal tryParse calls, default=False - Example:: + Example: - integer = Word(nums).set_parse_action(lambda toks: int(toks[0])) - year_int = integer.copy() - year_int.add_condition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later") - date_str = year_int + '/' + integer + '/' + integer + .. doctest:: + :options: +NORMALIZE_WHITESPACE - result = date_str.parse_string("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), - (line:1, col:1) + >>> integer = Word(nums).set_parse_action(lambda toks: int(toks[0])) + >>> year_int = integer.copy().add_condition( + ... lambda toks: toks[0] >= 2000, + ... message="Only support years 2000 and later") + >>> date_str = year_int + '/' + integer + '/' + integer + + >>> result = date_str.parse_string("1999/12/31") + Traceback (most recent call last): + ParseException: Only support years 2000 and later... """ for fn in fns: self.parseAction.append( @@ -1029,12 +1109,14 @@ class ParserElement(ABC): @staticmethod def reset_cache() -> None: - ParserElement.packrat_cache.clear() - ParserElement.packrat_cache_stats[:] = [0] * len( - ParserElement.packrat_cache_stats - ) - ParserElement.recursion_memos.clear() + with ParserElement.packrat_cache_lock: + ParserElement.packrat_cache.clear() + ParserElement.packrat_cache_stats[:] = [0] * len( + ParserElement.packrat_cache_stats + ) + ParserElement.recursion_memos.clear() + # class attributes to keep caching status _packratEnabled = False _left_recursion_enabled = False @@ -1047,10 +1129,11 @@ class ParserElement(ABC): This makes it safe to call before activating Packrat nor Left Recursion to clear any previous settings. """ - ParserElement.reset_cache() - ParserElement._left_recursion_enabled = False - ParserElement._packratEnabled = False - ParserElement._parse = ParserElement._parseNoCache + with ParserElement.packrat_cache_lock: + ParserElement.reset_cache() + ParserElement._left_recursion_enabled = False + ParserElement._packratEnabled = False + ParserElement._parse = ParserElement._parseNoCache @staticmethod def enable_left_recursion( @@ -1062,17 +1145,26 @@ class ParserElement(ABC): repeatedly matched with a fixed recursion depth that is gradually increased until finding the longest match. - Example:: + Example: + + .. testcode:: import pyparsing as pp pp.ParserElement.enable_left_recursion() E = pp.Forward("E") num = pp.Word(pp.nums) + # match `num`, or `num '+' num`, or `num '+' num '+' num`, ... E <<= E + '+' - num | num - print(E.parse_string("1+2+3")) + print(E.parse_string("1+2+3+4")) + + prints: + + .. testoutput:: + + ['1', '+', '2', '+', '3', '+', '4'] Recursion search naturally memoizes matches of ``Forward`` elements and may thus skip reevaluation of parse actions during backtracking. This may break @@ -1088,17 +1180,18 @@ class ParserElement(ABC): thus the two cannot be used together. Use ``force=True`` to disable any previous, conflicting settings. """ - if force: - ParserElement.disable_memoization() - elif ParserElement._packratEnabled: - raise RuntimeError("Packrat and Bounded Recursion are not compatible") - if cache_size_limit is None: - ParserElement.recursion_memos = _UnboundedMemo() - elif cache_size_limit > 0: - ParserElement.recursion_memos = _LRUMemo(capacity=cache_size_limit) # type: ignore[assignment] - else: - raise NotImplementedError(f"Memo size of {cache_size_limit}") - ParserElement._left_recursion_enabled = True + with ParserElement.packrat_cache_lock: + if force: + ParserElement.disable_memoization() + elif ParserElement._packratEnabled: + raise RuntimeError("Packrat and Bounded Recursion are not compatible") + if cache_size_limit is None: + ParserElement.recursion_memos = _UnboundedMemo() + elif cache_size_limit > 0: + ParserElement.recursion_memos = _LRUMemo(capacity=cache_size_limit) # type: ignore[assignment] + else: + raise NotImplementedError(f"Memo size of {cache_size_limit}") + ParserElement._left_recursion_enabled = True @staticmethod def enable_packrat( @@ -1125,6 +1218,8 @@ class ParserElement(ABC): For best results, call ``enable_packrat()`` immediately after importing pyparsing. + .. Can't really be doctested, alas + Example:: import pyparsing @@ -1134,20 +1229,21 @@ class ParserElement(ABC): thus the two cannot be used together. Use ``force=True`` to disable any previous, conflicting settings. """ - if force: - ParserElement.disable_memoization() - elif ParserElement._left_recursion_enabled: - raise RuntimeError("Packrat and Bounded Recursion are not compatible") + with ParserElement.packrat_cache_lock: + if force: + ParserElement.disable_memoization() + elif ParserElement._left_recursion_enabled: + raise RuntimeError("Packrat and Bounded Recursion are not compatible") - if ParserElement._packratEnabled: - return + if ParserElement._packratEnabled: + return - ParserElement._packratEnabled = True - if cache_size_limit is None: - ParserElement.packrat_cache = _UnboundedCache() - else: - ParserElement.packrat_cache = _FifoCache(cache_size_limit) - ParserElement._parse = ParserElement._parseCache + ParserElement._packratEnabled = True + if cache_size_limit is None: + ParserElement.packrat_cache = _UnboundedCache() + else: + ParserElement.packrat_cache = _FifoCache(cache_size_limit) + ParserElement._parse = ParserElement._parseCache def parse_string( self, instring: str, parse_all: bool = False, *, parseAll: bool = False @@ -1180,19 +1276,22 @@ class ParserElement(ABC): By default, partial matches are OK. - >>> res = Word('a').parse_string('aaaaabaaa') - >>> print(res) - ['aaaaa'] + .. doctest:: + + >>> res = Word('a').parse_string('aaaaabaaa') + >>> print(res) + ['aaaaa'] The parsing behavior varies by the inheriting class of this abstract class. Please refer to the children directly to see more examples. It raises an exception if parse_all flag is set and instring does not match the whole grammar. - >>> res = Word('a').parse_string('aaaaabaaa', parse_all=True) - Traceback (most recent call last): - ... - pyparsing.ParseException: Expected end of text, found 'b' (at char 5), (line:1, col:6) + .. doctest:: + + >>> res = Word('a').parse_string('aaaaabaaa', parse_all=True) + Traceback (most recent call last): + ParseException: Expected end of text, found 'b' ... """ parseAll = parse_all or parseAll @@ -1240,7 +1339,9 @@ class ParserElement(ABC): being parsed. See :class:`parse_string` for more information on parsing strings with embedded tabs. - Example:: + Example: + + .. testcode:: source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" print(source) @@ -1248,7 +1349,9 @@ class ParserElement(ABC): print(' '*start + '^'*(end-start)) print(' '*start + tokens[0]) - prints:: + prints: + + .. testoutput:: sldjf123lsdjjkf345sldkjf879lkjsfd987 ^^^^^ @@ -1327,16 +1430,24 @@ class ParserElement(ABC): and replace the matched text patterns according to the logic in the parse action. ``transform_string()`` returns the resulting transformed string. - Example:: + Example: + + .. testcode:: + + quote = '''now is the winter of our discontent, + made glorious summer by this sun of york.''' wd = Word(alphas) wd.set_parse_action(lambda toks: toks[0].title()) - print(wd.transform_string("now is the winter of our discontent made glorious summer by this sun of york.")) + print(wd.transform_string(quote)) + + prints: - prints:: + .. testoutput:: - Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York. + Now Is The Winter Of Our Discontent, + Made Glorious Summer By This Sun Of York. """ out: list[str] = [] lastE = 0 @@ -1382,17 +1493,26 @@ class ParserElement(ABC): to match the given parse expression. May be called with optional ``max_matches`` argument, to clip searching after 'n' matches are found. - Example:: + Example: + + .. testcode:: - # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters + quote = '''More than Iron, more than Lead, + more than Gold I need Electricity''' + + # a capitalized word starts with an uppercase letter, + # followed by zero or more lowercase letters cap_word = Word(alphas.upper(), alphas.lower()) - print(cap_word.search_string("More than Iron, more than Lead, more than Gold I need Electricity")) + print(cap_word.search_string(quote)) + + # the sum() builtin can be used to merge results + # into a single ParseResults object + print(sum(cap_word.search_string(quote))) - # the sum() builtin can be used to merge results into a single ParseResults object - print(sum(cap_word.search_string("More than Iron, more than Lead, more than Gold I need Electricity"))) + prints: - prints:: + .. testoutput:: [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] @@ -1428,12 +1548,17 @@ class ParserElement(ABC): and the optional ``include_separators`` argument (default= ``False``), if the separating matching text should be included in the split results. - Example:: + Example: + + .. testcode:: punc = one_of(list(".,;:/-!?")) - print(list(punc.split("This, this?, this sentence, is badly punctuated!"))) + print(list(punc.split( + "This, this?, this sentence, is badly punctuated!"))) + + prints: - prints:: + .. testoutput:: ['This', ' this', '', ' this sentence', ' is badly punctuated', ''] """ @@ -1451,21 +1576,29 @@ class ParserElement(ABC): Implementation of ``+`` operator - returns :class:`And`. Adding strings to a :class:`ParserElement` converts them to :class:`Literal`\\ s by default. - Example:: + Example: + + .. testcode:: greet = Word(alphas) + "," + Word(alphas) + "!" hello = "Hello, World!" print(hello, "->", greet.parse_string(hello)) - prints:: + prints: + + .. testoutput:: Hello, World! -> ['Hello', ',', 'World', '!'] - ``...`` may be used as a parse expression as a short form of :class:`SkipTo`:: + ``...`` may be used as a parse expression as a short form of :class:`SkipTo`: + + .. testcode:: Literal('start') + ... + Literal('end') - is equivalent to:: + is equivalent to: + + .. testcode:: Literal('start') + SkipTo('end')("_skipped*") + Literal('end') @@ -1601,6 +1734,9 @@ class ParserElement(ABC): def __or__(self, other) -> ParserElement: """ Implementation of ``|`` operator - returns :class:`MatchFirst` + + .. versionchanged:: 3.1.0 + Support ``expr | ""`` as a synonym for ``Optional(expr)``. """ if other is Ellipsis: return _PendingSkip(self, must_skip=True) @@ -1699,6 +1835,8 @@ class ParserElement(ABC): - ``expr[...: end_expr]`` and ``expr[0, ...: end_expr]`` are equivalent to ``ZeroOrMore(expr, stop_on=end_expr)`` - ``expr[1, ...: end_expr]`` is equivalent to ``OneOrMore(expr, stop_on=end_expr)`` + .. versionchanged:: 3.1.0 + Support for slice notation. """ stop_on_defined = False @@ -1743,10 +1881,16 @@ class ParserElement(ABC): If ``name`` is omitted, same as calling :class:`copy`. - Example:: + Example: + + .. testcode:: # these are equivalent - userdata = Word(alphas).set_results_name("name") + Word(nums + "-").set_results_name("socsecno") + userdata = ( + Word(alphas).set_results_name("name") + + Word(nums + "-").set_results_name("socsecno") + ) + userdata = Word(alphas)("name") + Word(nums + "-")("socsecno") """ if name is not None: @@ -1808,15 +1952,17 @@ class ParserElement(ABC): matching; may be called repeatedly, to define multiple comment or other ignorable patterns. - Example:: + Example: + + .. doctest:: - patt = Word(alphas)[...] - patt.parse_string('ablaj /* comment */ lskjd') - # -> ['ablaj'] + >>> patt = Word(alphas)[...] + >>> print(patt.parse_string('ablaj /* comment */ lskjd')) + ['ablaj'] - patt.ignore(c_style_comment) - patt.parse_string('ablaj /* comment */ lskjd') - # -> ['ablaj', 'lskjd'] + >>> patt = Word(alphas)[...].ignore(c_style_comment) + >>> print(patt.parse_string('ablaj /* comment */ lskjd')) + ['ablaj', 'lskjd'] """ if isinstance(other, str_type): other = Suppress(other) @@ -1837,14 +1983,32 @@ class ParserElement(ABC): """ Customize display of debugging messages while doing pattern matching: - - ``start_action`` - method to be called when an expression is about to be parsed; - should have the signature ``fn(input_string: str, location: int, expression: ParserElement, cache_hit: bool)`` + :param start_action: method to be called when an expression is about to be parsed; + should have the signature:: + + fn(input_string: str, + location: int, + expression: ParserElement, + cache_hit: bool) - - ``success_action`` - method to be called when an expression has successfully parsed; - should have the signature ``fn(input_string: str, start_location: int, end_location: int, expression: ParserELement, parsed_tokens: ParseResults, cache_hit: bool)`` + :param success_action: method to be called when an expression has successfully parsed; + should have the signature:: + + fn(input_string: str, + start_location: int, + end_location: int, + expression: ParserELement, + parsed_tokens: ParseResults, + cache_hit: bool) - - ``exception_action`` - method to be called when expression fails to parse; - should have the signature ``fn(input_string: str, location: int, expression: ParserElement, exception: Exception, cache_hit: bool)`` + :param exception_action: method to be called when expression fails to parse; + should have the signature:: + + fn(input_string: str, + location: int, + expression: ParserElement, + exception: Exception, + cache_hit: bool) """ self.debugActions = self.DebugActions( start_action or _default_start_debug_action, # type: ignore[truthy-function] @@ -1860,7 +2024,9 @@ class ParserElement(ABC): Set ``flag`` to ``True`` to enable, ``False`` to disable. Set ``recurse`` to ``True`` to set the debug flag on this expression and all sub-expressions. - Example:: + Example: + + .. testcode:: wd = Word(alphas).set_name("alphaword") integer = Word(nums).set_name("numword") @@ -1871,26 +2037,41 @@ class ParserElement(ABC): term[1, ...].parse_string("abc 123 xyz 890") - prints:: + prints: + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE Match alphaword at loc 0(1,1) + abc 123 xyz 890 + ^ Matched alphaword -> ['abc'] - Match alphaword at loc 3(1,4) - Exception raised:Expected alphaword (at char 4), (line:1, col:5) - Match alphaword at loc 7(1,8) + Match alphaword at loc 4(1,5) + abc 123 xyz 890 + ^ + Match alphaword failed, ParseException raised: Expected alphaword, ... + Match alphaword at loc 8(1,9) + abc 123 xyz 890 + ^ Matched alphaword -> ['xyz'] - Match alphaword at loc 11(1,12) - Exception raised:Expected alphaword (at char 12), (line:1, col:13) - Match alphaword at loc 15(1,16) - Exception raised:Expected alphaword (at char 15), (line:1, col:16) + Match alphaword at loc 12(1,13) + abc 123 xyz 890 + ^ + Match alphaword failed, ParseException raised: Expected alphaword, ... + abc 123 xyz 890 + ^ + Match alphaword failed, ParseException raised: Expected alphaword, found end of text ... The output shown is that produced by the default debug actions - custom debug actions can be - specified using :class:`set_debug_actions`. Prior to attempting + specified using :meth:`set_debug_actions`. Prior to attempting to match the ``wd`` expression, the debugging message ``"Match <exprname> at loc <n>(<line>,<col>)"`` is shown. Then if the parse succeeds, a ``"Matched"`` message is shown, or an ``"Exception raised"`` - message is shown. Also note the use of :class:`set_name` to assign a human-readable name to the expression, + message is shown. Also note the use of :meth:`set_name` to assign a human-readable name to the expression, which makes debugging and exception messages easier to understand - for instance, the default - name created for the :class:`Word` expression without calling ``set_name`` is ``"W:(A-Za-z)"``. + name created for the :class:`Word` expression without calling :meth:`set_name` is ``"W:(A-Za-z)"``. + + .. versionchanged:: 3.1.0 + ``recurse`` argument added. """ if recurse: for expr in self.visit_all(): @@ -1928,13 +2109,23 @@ class ParserElement(ABC): If `name` is None, clears any custom name for this expression, and clears the debug flag is it was enabled via `__diag__.enable_debug_on_named_expressions`. - Example:: + Example: - integer = Word(nums) - integer.parse_string("ABC") # -> Exception: Expected W:(0-9) (at char 0), (line:1, col:1) + .. doctest:: - integer.set_name("integer") - integer.parse_string("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1) + >>> integer = Word(nums) + >>> integer.parse_string("ABC") + Traceback (most recent call last): + ParseException: Expected W:(0-9) (at char 0), (line:1, col:1) + + >>> integer.set_name("integer") + integer + >>> integer.parse_string("ABC") + Traceback (most recent call last): + ParseException: Expected integer (at char 0), (line:1, col:1) + + .. versionchanged:: 3.1.0 + Accept ``None`` as the ``name`` argument. """ self.customName = name # type: ignore[assignment] self.errmsg = f"Expected {str(self)}" @@ -1974,7 +2165,11 @@ class ParserElement(ABC): def validate(self, validateTrace=None) -> None: """ + .. deprecated:: 3.0.0 + Do not use to check for left recursion. + Check defined expressions for valid structure, check for infinite recursive definitions. + """ warnings.warn( "ParserElement.validate() is deprecated, and should not be used to check for left recursion", @@ -2032,15 +2227,16 @@ class ParserElement(ABC): Method for quick testing of a parser against a test string. Good for simple inline microtests of sub expressions while building up larger parser. - Parameters: + :param test_string: to test against this expression for a match + :param parse_all: flag to pass to :meth:`parse_string` when running tests - - ``test_string`` - to test against this expression for a match - - ``parse_all`` - (default= ``True``) - flag to pass to :class:`parse_string` when running tests + Example: - Example:: + .. doctest:: - expr = Word(nums) - assert expr.matches("100") + >>> expr = Word(nums) + >>> expr.matches("100") + True """ parseAll = parseAll and parse_all try: @@ -2096,7 +2292,9 @@ class ParserElement(ABC): (or failed if ``failure_tests`` is True), and the results contain a list of lines of each test's output - Example:: + Passing example: + + .. testcode:: number_expr = pyparsing_common.number.copy() @@ -2109,20 +2307,16 @@ class ParserElement(ABC): 6.02e23 # integer with scientific notation 1e-12 + # negative decimal number without leading digit + -.100 ''') print("Success" if result[0] else "Failed!") - result = number_expr.run_tests(''' - # stray character - 100Z - # missing leading digit before '.' - -.100 - # too many '.' - 3.14.159 - ''', failure_tests=True) - print("Success" if result[0] else "Failed!") + prints: + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE - prints:: # unsigned integer 100 @@ -2140,30 +2334,59 @@ class ParserElement(ABC): 1e-12 [1e-12] + # negative decimal number without leading digit + -.100 + [-0.1] Success + Failure-test example: + + .. testcode:: + + result = number_expr.run_tests(''' + # stray character + 100Z + # too many '.' + 3.14.159 + ''', failure_tests=True) + print("Success" if result[0] else "Failed!") + + prints: + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + + # stray character 100Z + 100Z ^ - FAIL: Expected end of text (at char 3), (line:1, col:4) - - # missing leading digit before '.' - -.100 - ^ - FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1) + ParseException: Expected end of text, found 'Z' ... # too many '.' 3.14.159 + 3.14.159 ^ - FAIL: Expected end of text (at char 4), (line:1, col:5) - + ParseException: Expected end of text, found '.' ... + FAIL: Expected end of text, found '.' ... Success Each test string must be on a single line. If you want to test a string that spans multiple - lines, create a test like this:: + lines, create a test like this: + .. testcode:: + + expr = Word(alphanums)[1,...] expr.run_tests(r"this is a test\\n of strings that spans \\n 3 lines") + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + :hide: + + + this is a test\\n of strings that spans \\n 3 lines + ['this', 'is', 'a', 'test', 'of', 'strings', 'that', 'spans', '3', 'lines'] + (Note that this is a raw string literal, you must include the leading ``'r'``.) """ from .testing import pyparsing_test @@ -2296,6 +2519,9 @@ class ParserElement(ABC): Additional diagram-formatting keyword arguments can also be included; see railroad.Diagram class. + + .. versionchanged:: 3.1.0 + ``embed`` argument added. """ try: @@ -2431,11 +2657,17 @@ class Literal(Token): """ Token to exactly match a specified string. - Example:: + Example: - Literal('abc').parse_string('abc') # -> ['abc'] - Literal('abc').parse_string('abcdef') # -> ['abc'] - Literal('abc').parse_string('ab') # -> Exception: Expected "abc" + .. doctest:: + + >>> Literal('abc').parse_string('abc') + ParseResults(['abc'], {}) + >>> Literal('abc').parse_string('abcdef') + ParseResults(['abc'], {}) + >>> Literal('abc').parse_string('ab') + Traceback (most recent call last): + ParseException: Expected 'abc', found 'ab' (at char 0), (line: 1, col: 1) For case-insensitive matching, use :class:`CaselessLiteral`. @@ -2526,10 +2758,25 @@ class Keyword(Token): "$" - ``caseless`` allows case-insensitive matching, default is ``False``. - Example:: + Example: + + .. doctest:: + :options: +NORMALIZE_WHITESPACE - Keyword("start").parse_string("start") # -> ['start'] - Keyword("start").parse_string("starting") # -> Exception + >>> Keyword("start").parse_string("start") + ParseResults(['start'], {}) + >>> Keyword("start").parse_string("starting") + Traceback (most recent call last): + ParseException: Expected Keyword 'start', keyword was immediately + followed by keyword character, found 'ing' (at char 5), (line:1, col:6) + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> Keyword("start").parse_string("starting").debug() + Traceback (most recent call last): + ParseException: Expected Keyword "start", keyword was immediately + followed by keyword character, found 'ing' ... For case-insensitive matching, use :class:`CaselessKeyword`. """ @@ -2630,10 +2877,12 @@ class CaselessLiteral(Literal): Note: the matched results will always be in the case of the given match string, NOT the case of the input text. - Example:: + Example: + + .. doctest:: - CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10") - # -> ['CMD', 'CMD', 'CMD'] + >>> CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10") + ParseResults(['CMD', 'CMD', 'CMD'], {}) (Contrast with example for :class:`CaselessKeyword`.) """ @@ -2655,10 +2904,12 @@ class CaselessKeyword(Keyword): """ Caseless version of :class:`Keyword`. - Example:: + Example: - CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10") - # -> ['CMD', 'CMD'] + .. doctest:: + + >>> CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10") + ParseResults(['CMD', 'CMD'], {}) (Contrast with example for :class:`CaselessLiteral`.) """ @@ -2697,18 +2948,31 @@ class CloseMatch(Token): If ``mismatches`` is an empty list, then the match was an exact match. - Example:: + Example: + + .. doctest:: + :options: +NORMALIZE_WHITESPACE - patt = CloseMatch("ATCATCGAATGGA") - patt.parse_string("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']}) - patt.parse_string("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1) + >>> patt = CloseMatch("ATCATCGAATGGA") + >>> patt.parse_string("ATCATCGAAXGGA") + ParseResults(['ATCATCGAAXGGA'], + {'original': 'ATCATCGAATGGA', 'mismatches': [9]}) + + >>> patt.parse_string("ATCAXCGAAXGGA") + Traceback (most recent call last): + ParseException: Expected 'ATCATCGAATGGA' (with up to 1 mismatches), + found 'ATCAXCGAAXGGA' (at char 0), (line:1, col:1) # exact match - patt.parse_string("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']}) + >>> patt.parse_string("ATCATCGAATGGA") + ParseResults(['ATCATCGAATGGA'], + {'original': 'ATCATCGAATGGA', 'mismatches': []}) # close match allowing up to 2 mismatches - patt = CloseMatch("ATCATCGAATGGA", max_mismatches=2) - patt.parse_string("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) + >>> patt = CloseMatch("ATCATCGAATGGA", max_mismatches=2) + >>> patt.parse_string("ATCAXCGAAXGGA") + ParseResults(['ATCAXCGAAXGGA'], + {'original': 'ATCATCGAATGGA', 'mismatches': [4, 9]}) """ def __init__( @@ -2799,23 +3063,28 @@ class Word(Token): pyparsing includes helper strings for building Words: - - :class:`alphas` - - :class:`nums` - - :class:`alphanums` - - :class:`hexnums` - - :class:`alphas8bit` (alphabetic characters in ASCII range 128-255 + - :attr:`alphas` + - :attr:`nums` + - :attr:`alphanums` + - :attr:`hexnums` + - :attr:`alphas8bit` (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.) - - :class:`punc8bit` (non-alphabetic characters in ASCII range + - :attr:`punc8bit` (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.) - - :class:`printables` (any non-whitespace character) + - :attr:`printables` (any non-whitespace character) ``alphas``, ``nums``, and ``printables`` are also defined in several - Unicode sets - see :class:`pyparsing_unicode``. + Unicode sets - see :class:`pyparsing_unicode`. - Example:: + Example: + + .. testcode:: # a word composed of digits - integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9")) + integer = Word(nums) + # Two equivalent alternate forms: + Word("0123456789") + Word(srange("[0-9]")) # a word with a leading capital, and zero or more lowercase capitalized_word = Word(alphas.upper(), alphas.lower()) @@ -2823,11 +3092,18 @@ class Word(Token): # hostnames are alphanumeric, with leading alpha, and '-' hostname = Word(alphas, alphanums + '-') - # roman numeral (not a strict parser, accepts invalid mix of characters) + # roman numeral + # (not a strict parser, accepts invalid mix of characters) roman = Word("IVXLCDM") # any string of non-whitespace characters, except for ',' csv_value = Word(printables, exclude_chars=",") + + :raises ValueError: If ``min`` and ``max`` are both specified + and the test ``min <= max`` fails. + + .. versionchanged:: 3.1.0 + Raises :exc:`ValueError` if ``min`` > ``max``. """ def __init__( @@ -2948,6 +3224,13 @@ class Word(Token): self.re_match = self.re.match self.parseImpl = self.parseImpl_regex # type: ignore[method-assign] + def copy(self) -> Word: + ret: Word = cast(Word, super().copy()) + if hasattr(self, "re_match"): + ret.re_match = self.re_match + ret.parseImpl = ret.parseImpl_regex # type: ignore[method-assign] + return ret + def _generateDefaultName(self) -> str: def charsAsStr(s): max_repr_len = 16 @@ -3047,7 +3330,14 @@ class Regex(Token): (such as the ``regex`` module), you can do so by building your ``Regex`` object with a compiled RE that was compiled using ``regex``. - Example:: + The parameters ``pattern`` and ``flags`` are passed + to the ``re.compile()`` function as-is. See the Python + `re module <https://docs.python.org/3/library/re.html>`_ module for an + explanation of the acceptable patterns and flags. + + Example: + + .. testcode:: realnum = Regex(r"[+-]?\d+\.\d*") # ref: https://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression @@ -3056,9 +3346,10 @@ class Regex(Token): # named fields in a regex will be returned as named results date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\d\d?)') - # the Regex class will accept re's compiled using the regex module - import regex - parser = pp.Regex(regex.compile(r'[0-9]')) + # the Regex class will accept regular expressions compiled using the + # re module + import re + parser = pp.Regex(re.compile(r'[0-9]')) """ def __init__( @@ -3071,11 +3362,6 @@ class Regex(Token): asGroupList: bool = False, asMatch: bool = False, ) -> None: - """The parameters ``pattern`` and ``flags`` are passed - to the ``re.compile()`` function as-is. See the Python - `re module <https://docs.python.org/3/library/re.html>`_ module for an - explanation of the acceptable patterns and flags. - """ super().__init__() asGroupList = asGroupList or as_group_list asMatch = asMatch or as_match @@ -3116,6 +3402,14 @@ class Regex(Token): if self.asMatch: self.parseImpl = self.parseImplAsMatch # type: ignore [method-assign] + def copy(self): + ret: Regex = cast(Regex, super().copy()) + if self.asGroupList: + ret.parseImpl = ret.parseImplAsGroupList + if self.asMatch: + ret.parseImpl = ret.parseImplAsMatch + return ret + @cached_property def re(self) -> re.Pattern: if self._re: @@ -3207,11 +3501,16 @@ class Regex(Token): Return :class:`Regex` with an attached parse action to transform the parsed result as if called using `re.sub(expr, repl, string) <https://docs.python.org/3/library/re.html#re.sub>`_. - Example:: + Example: + + .. testcode:: make_html = Regex(r"(\w+):(.*?):").sub(r"<\1>\2</\1>") print(make_html.transform_string("h1:main title:")) - # prints "<h1>main title</h1>" + + .. testoutput:: + + <h1>main title</h1> """ if self.asGroupList: raise TypeError("cannot use sub() with Regex(as_group_list=True)") @@ -3258,19 +3557,23 @@ class QuotedString(Token): (``'\t'``, ``'\n'``, etc.) to actual whitespace (default= ``True``) - Example:: + .. caution:: ``convert_whitespace_escapes`` has no effect if + ``unquote_results`` is ``False``. - qs = QuotedString('"') - print(qs.search_string('lsjdf "This is the quote" sldjf')) - complex_qs = QuotedString('{{', end_quote_char='}}') - print(complex_qs.search_string('lsjdf {{This is the "quote"}} sldjf')) - sql_qs = QuotedString('"', esc_quote='""') - print(sql_qs.search_string('lsjdf "This is the quote with ""embedded"" quotes" sldjf')) + Example: - prints:: + .. doctest:: + >>> qs = QuotedString('"') + >>> print(qs.search_string('lsjdf "This is the quote" sldjf')) [['This is the quote']] + >>> complex_qs = QuotedString('{{', end_quote_char='}}') + >>> print(complex_qs.search_string( + ... 'lsjdf {{This is the "quote"}} sldjf')) [['This is the "quote"']] + >>> sql_qs = QuotedString('"', esc_quote='""') + >>> print(sql_qs.search_string( + ... 'lsjdf "This is the quote with ""embedded"" quotes" sldjf')) [['This is the quote with "embedded" quotes']] """ @@ -3480,13 +3783,21 @@ class CharsNotIn(Token): ``max`` and ``exact`` are 0, meaning no maximum or exact length restriction. - Example:: + Example: + + .. testcode:: # define a comma-separated-value as anything that is not a ',' csv_value = CharsNotIn(',') - print(DelimitedList(csv_value).parse_string("dkls,lsdkjf,s12 34,@!#,213")) + print( + DelimitedList(csv_value).parse_string( + "dkls,lsdkjf,s12 34,@!#,213" + ) + ) - prints:: + prints: + + .. testoutput:: ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] """ @@ -3674,22 +3985,27 @@ class LineStart(PositionToken): r"""Matches if current position is at the beginning of a line within the parse string - Example:: + Example: + + .. testcode:: test = '''\ AAA this line AAA and this line - AAA but not this one - B AAA and definitely not this one + AAA and even this line + B AAA but definitely not this line ''' for t in (LineStart() + 'AAA' + rest_of_line).search_string(test): print(t) - prints:: + prints: + + .. testoutput:: ['AAA', ' this line'] ['AAA', ' and this line'] + ['AAA', ' and even this line'] """ @@ -3843,23 +4159,23 @@ class Tag(Token): processing the parsed results. Accepts an optional tag value, defaulting to `True`. - Example:: - - end_punc = "." | ("!" + Tag("enthusiastic"))) - greeting = "Hello," + Word(alphas) + end_punc + Example: - result = greeting.parse_string("Hello, World.") - print(result.dump()) + .. doctest:: - result = greeting.parse_string("Hello, World!") - print(result.dump()) - - prints:: + >>> end_punc = "." | ("!" + Tag("enthusiastic")) + >>> greeting = "Hello," + Word(alphas) + end_punc + >>> result = greeting.parse_string("Hello, World.") + >>> print(result.dump()) ['Hello,', 'World', '.'] + >>> result = greeting.parse_string("Hello, World!") + >>> print(result.dump()) ['Hello,', 'World', '!'] - enthusiastic: True + + .. versionadded:: 3.1.0 """ def __init__(self, tag_name: str, value: Any = True) -> None: @@ -4060,7 +4376,9 @@ class And(ParseExpression): May also be constructed using the ``'-'`` operator, which will suppress backtracking. - Example:: + Example: + + .. testcode:: integer = Word(nums) name_expr = Word(alphas)[1, ...] @@ -4226,14 +4544,18 @@ class Or(ParseExpression): string will be used. May be constructed using the ``'^'`` operator. - Example:: + Example: + + .. testcode:: # construct Or using '^' operator number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums)) print(number.search_string("123 3.1416 789")) - prints:: + prints: + + .. testoutput:: [['123'], ['3.1416'], ['789']] """ @@ -4383,17 +4705,19 @@ class MatchFirst(ParseExpression): more than one expression matches, the first one listed is the one that will match. May be constructed using the ``'|'`` operator. - Example:: + Example: Construct MatchFirst using '|' operator - # construct MatchFirst using '|' operator + .. doctest:: # watch the order of expressions to match - number = Word(nums) | Combine(Word(nums) + '.' + Word(nums)) - print(number.search_string("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']] + >>> number = Word(nums) | Combine(Word(nums) + '.' + Word(nums)) + >>> print(number.search_string("123 3.1416 789")) # Fail! + [['123'], ['3'], ['1416'], ['789']] # put more selective expression first - number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) - print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] + >>> number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) + >>> print(number.search_string("123 3.1416 789")) # Better + [['123'], ['3.1416'], ['789']] """ def __init__( @@ -4494,7 +4818,9 @@ class Each(ParseExpression): May be constructed using the ``'&'`` operator. - Example:: + Example: + + .. testcode:: color = one_of("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN") shape_type = one_of("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON") @@ -4515,35 +4841,42 @@ class Each(ParseExpression): ''' ) - prints:: + prints: + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + shape: SQUARE color: BLACK posn: 100, 120 ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']] - - color: BLACK + - color: 'BLACK' - posn: ['100', ',', '120'] - - x: 100 - - y: 120 - - shape: SQUARE - + - x: '100' + - y: '120' + - shape: 'SQUARE' + ... shape: CIRCLE size: 50 color: BLUE posn: 50,80 - ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']] - - color: BLUE + ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', + 'posn:', ['50', ',', '80']] + - color: 'BLUE' - posn: ['50', ',', '80'] - - x: 50 - - y: 80 - - shape: CIRCLE - - size: 50 - + - x: '50' + - y: '80' + - shape: 'CIRCLE' + - size: '50' + ... - color: GREEN size: 20 shape: TRIANGLE posn: 20,40 - ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']] - - color: GREEN + color:GREEN size:20 shape:TRIANGLE posn:20,40 + ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', + 'posn:', ['20', ',', '40']] + - color: 'GREEN' - posn: ['20', ',', '40'] - - x: 20 - - y: 40 - - shape: TRIANGLE - - size: 20 + - x: '20' + - y: '40' + - shape: 'TRIANGLE' + - size: '20' + ... """ def __init__( @@ -4863,23 +5196,26 @@ class AtLineStart(ParseElementEnhance): r"""Matches if an expression matches at the beginning of a line within the parse string - Example:: + Example: + + .. testcode:: test = '''\ - AAA this line - AAA and this line - AAA but not this one - B AAA and definitely not this one + BBB this line + BBB and this line + BBB but not this one + A BBB and definitely not this one ''' - for t in (AtLineStart('AAA') + rest_of_line).search_string(test): + for t in (AtLineStart('BBB') + rest_of_line).search_string(test): print(t) - prints:: + prints: - ['AAA', ' this line'] - ['AAA', ' and this line'] + .. testoutput:: + ['BBB', ' this line'] + ['BBB', ' and this line'] """ def __init__(self, expr: Union[ParserElement, str]) -> None: @@ -4901,16 +5237,24 @@ class FollowedBy(ParseElementEnhance): in the lookahead expression, those *will* be returned for access by name. - Example:: + Example: + + .. testcode:: # use FollowedBy to match a label only if it is followed by a ':' data_word = Word(alphas) label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + attr_expr = Group( + label + Suppress(':') + + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join) + ) - attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint() + attr_expr[1, ...].parse_string( + "shape: SQUARE color: BLACK posn: upper left").pprint() - prints:: + prints: + + .. testoutput:: [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] """ @@ -4950,12 +5294,13 @@ class PrecededBy(ParseElementEnhance): give a maximum number of characters to look back from the current parse position for a lookbehind match. - Example:: + Example: + + .. testcode:: # VB-style variable names with type prefixes int_var = PrecededBy("#") + pyparsing_common.identifier str_var = PrecededBy("$") + pyparsing_common.identifier - """ def __init__(self, expr: Union[ParserElement, str], retreat: int = 0) -> None: @@ -5023,18 +5368,21 @@ class Located(ParseElementEnhance): Be careful if the input text contains ``<TAB>`` characters, you may want to call :class:`ParserElement.parse_with_tabs` - Example:: + Example: + + .. testcode:: wd = Word(alphas) for match in Located(wd).search_string("ljsdf123lksdjjf123lkkjj1222"): print(match) - prints:: + prints: + + .. testoutput:: [0, ['ljsdf'], 5] [8, ['lksdjjf'], 15] [18, ['lkkjj'], 23] - """ def parseImpl(self, instring, loc, do_actions=True) -> ParseImplReturnType: @@ -5060,7 +5408,9 @@ class NotAny(ParseElementEnhance): *not* skip over leading whitespace. ``NotAny`` always returns a null token list. May be constructed using the ``'~'`` operator. - Example:: + Example: + + .. testcode:: AND, OR, NOT = map(CaselessKeyword, "AND OR NOT".split()) @@ -5182,21 +5532,34 @@ class OneOrMore(_MultipleMatch): (only required if the sentinel would ordinarily match the repetition expression) - Example:: + Example: - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join)) + .. doctest:: - text = "shape: SQUARE posn: upper left color: BLACK" - attr_expr[1, ...].parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']] + >>> data_word = Word(alphas) + >>> label = data_word + FollowedBy(':') + >>> attr_expr = Group( + ... label + Suppress(':') + ... + OneOrMore(data_word).set_parse_action(' '.join)) - # use stop_on attribute for OneOrMore to avoid reading label string as part of the data - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) - OneOrMore(attr_expr).parse_string(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] + >>> text = "shape: SQUARE posn: upper left color: BLACK" + + # Fail! read 'posn' as data instead of next label + >>> attr_expr[1, ...].parse_string(text).pprint() + [['shape', 'SQUARE posn']] + + # use stop_on attribute for OneOrMore + # to avoid reading label string as part of the data + >>> attr_expr = Group( + ... label + Suppress(':') + ... + OneOrMore( + ... data_word, stop_on=label).set_parse_action(' '.join)) + >>> OneOrMore(attr_expr).parse_string(text).pprint() # Better + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] # could also be written as - (attr_expr * (1,)).parse_string(text).pprint() + >>> (attr_expr * (1,)).parse_string(text).pprint() + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] """ def _generateDefaultName(self) -> str: @@ -5238,6 +5601,31 @@ class ZeroOrMore(_MultipleMatch): class DelimitedList(ParseElementEnhance): + """Helper to define a delimited list of expressions - the delimiter + defaults to ','. By default, the list elements and delimiters can + have intervening whitespace, and comments, but this can be + overridden by passing ``combine=True`` in the constructor. If + ``combine`` is set to ``True``, the matching tokens are + returned as a single token string, with the delimiters included; + otherwise, the matching tokens are returned as a list of tokens, + with the delimiters suppressed. + + If ``allow_trailing_delim`` is set to True, then the list may end with + a delimiter. + + Example: + + .. doctest:: + + >>> DelimitedList(Word(alphas)).parse_string("aa,bb,cc") + ParseResults(['aa', 'bb', 'cc'], {}) + >>> DelimitedList(Word(hexnums), delim=':', combine=True + ... ).parse_string("AA:BB:CC:DD:EE") + ParseResults(['AA:BB:CC:DD:EE'], {}) + + .. versionadded:: 3.1.0 + """ + def __init__( self, expr: Union[str, ParserElement], @@ -5248,23 +5636,6 @@ class DelimitedList(ParseElementEnhance): *, allow_trailing_delim: bool = False, ) -> None: - """Helper to define a delimited list of expressions - the delimiter - defaults to ','. By default, the list elements and delimiters can - have intervening whitespace, and comments, but this can be - overridden by passing ``combine=True`` in the constructor. If - ``combine`` is set to ``True``, the matching tokens are - returned as a single token string, with the delimiters included; - otherwise, the matching tokens are returned as a list of tokens, - with the delimiters suppressed. - - If ``allow_trailing_delim`` is set to True, then the list may end with - a delimiter. - - Example:: - - DelimitedList(Word(alphas)).parse_string("aa,bb,cc") # -> ['aa', 'bb', 'cc'] - DelimitedList(Word(hexnums), delim=':', combine=True).parse_string("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] - """ if isinstance(expr, str_type): expr = ParserElement._literalStringClass(expr) expr = typing.cast(ParserElement, expr) @@ -5314,12 +5685,13 @@ class Opt(ParseElementEnhance): """ Optional matching of the given expression. - Parameters: + :param expr: expression that must match zero or more times + :param default: (optional) - value to be returned + if the optional expression is not found. - - ``expr`` - expression that must match zero or more times - - ``default`` (optional) - value to be returned if the optional expression is not found. + Example: - Example:: + .. testcode:: # US postal code can be a 5-digit zip, plus optional 4-digit qualifier zip = Combine(Word(nums, exact=5) + Opt('-' + Word(nums, exact=4))) @@ -5334,7 +5706,11 @@ class Opt(ParseElementEnhance): 98765- ''') - prints:: + prints: + + .. testoutput:: + :options: +NORMALIZE_WHITESPACE + # traditional ZIP code 12345 @@ -5346,8 +5722,10 @@ class Opt(ParseElementEnhance): # invalid ZIP 98765- + 98765- ^ - FAIL: Expected end of text (at char 5), (line:1, col:6) + ParseException: Expected end of text, found '-' (at char 5), (line:1, col:6) + FAIL: Expected end of text, found '-' (at char 5), (line:1, col:6) """ __optionalNotMatched = _NullToken() @@ -5394,19 +5772,23 @@ class SkipTo(ParseElementEnhance): Token for skipping over all undefined text until the matched expression is found. - Parameters: + :param expr: target expression marking the end of the data to be skipped + :param include: if ``True``, the target expression is also parsed + (the skipped text and target expression are returned + as a 2-element list) (default= ``False``). + + :param ignore: (default= ``None``) used to define grammars + (typically quoted strings and comments) + that might contain false matches to the target expression - - ``expr`` - target expression marking the end of the data to be skipped - - ``include`` - if ``True``, the target expression is also parsed - (the skipped text and target expression are returned as a 2-element - list) (default= ``False``). - - ``ignore`` - (default= ``None``) used to define grammars (typically quoted strings and - comments) that might contain false matches to the target expression - - ``fail_on`` - (default= ``None``) define expressions that are not allowed to be - included in the skipped test; if found before the target expression is found, - the :class:`SkipTo` is not a match + :param fail_on: (default= ``None``) define expressions that + are not allowed to be included in the skipped test; + if found before the target expression is found, + the :class:`SkipTo` is not a match - Example:: + Example: + + .. testcode:: report = ''' Outstanding Issues Report - 1 Jan 2000 @@ -5430,9 +5812,11 @@ class SkipTo(ParseElementEnhance): + integer("days_open")) for tkt in ticket_expr.search_string(report): - print tkt.dump() + print(tkt.dump()) - prints:: + prints: + + .. testoutput:: ['101', 'Critical', 'Intermittent system crash', '6'] - days_open: '6' @@ -5546,28 +5930,31 @@ class Forward(ParseElementEnhance): Forward declaration of an expression to be defined later - used for recursive grammars, such as algebraic infix notation. When the expression is known, it is assigned to the ``Forward`` - variable using the ``'<<'`` operator. + instance using the ``'<<'`` operator. + + .. Note:: - Note: take care when assigning to ``Forward`` not to overlook - precedence of operators. + Take care when assigning to ``Forward`` not to overlook + precedence of operators. - Specifically, ``'|'`` has a lower precedence than ``'<<'``, so that:: + Specifically, ``'|'`` has a lower precedence than ``'<<'``, so that:: - fwd_expr << a | b | c + fwd_expr << a | b | c - will actually be evaluated as:: + will actually be evaluated as:: - (fwd_expr << a) | b | c + (fwd_expr << a) | b | c - thereby leaving b and c out as parseable alternatives. It is recommended that you - explicitly group the values inserted into the ``Forward``:: + thereby leaving b and c out as parseable alternatives. + It is recommended that you explicitly group the values + inserted into the :class:`Forward`:: - fwd_expr << (a | b | c) + fwd_expr << (a | b | c) - Converting to use the ``'<<='`` operator instead will avoid this problem. + Converting to use the ``'<<='`` operator instead will avoid this problem. - See :class:`ParseResults.pprint` for an example of a recursive - parser created using ``Forward``. + See :meth:`ParseResults.pprint` for an example of a recursive + parser created using :class:`Forward`. """ def __init__( @@ -5826,17 +6213,26 @@ class Combine(TokenConverter): input string; this can be disabled by specifying ``'adjacent=False'`` in the constructor. - Example:: + Example: + + .. doctest:: + + >>> real = Word(nums) + '.' + Word(nums) + >>> print(real.parse_string('3.1416')) + ['3', '.', '1416'] + + >>> # will also erroneously match the following + >>> print(real.parse_string('3. 1416')) + ['3', '.', '1416'] - real = Word(nums) + '.' + Word(nums) - print(real.parse_string('3.1416')) # -> ['3', '.', '1416'] - # will also erroneously match the following - print(real.parse_string('3. 1416')) # -> ['3', '.', '1416'] + >>> real = Combine(Word(nums) + '.' + Word(nums)) + >>> print(real.parse_string('3.1416')) + ['3.1416'] - real = Combine(Word(nums) + '.' + Word(nums)) - print(real.parse_string('3.1416')) # -> ['3.1416'] - # no match when there are internal spaces - print(real.parse_string('3. 1416')) # -> Exception: Expected W:(0123...) + >>> # no match when there are internal spaces + >>> print(real.parse_string('3. 1416')) + Traceback (most recent call last): + ParseException: Expected W:(0123...) """ def __init__( @@ -5884,18 +6280,20 @@ class Group(TokenConverter): The optional ``aslist`` argument when set to True will return the parsed tokens as a Python list instead of a pyparsing ParseResults. - Example:: + Example: + + .. doctest:: - ident = Word(alphas) - num = Word(nums) - term = ident | num - func = ident + Opt(DelimitedList(term)) - print(func.parse_string("fn a, b, 100")) - # -> ['fn', 'a', 'b', '100'] + >>> ident = Word(alphas) + >>> num = Word(nums) + >>> term = ident | num + >>> func = ident + Opt(DelimitedList(term)) + >>> print(func.parse_string("fn a, b, 100")) + ['fn', 'a', 'b', '100'] - func = ident + Group(Opt(DelimitedList(term))) - print(func.parse_string("fn a, b, 100")) - # -> ['fn', ['a', 'b', '100']] + >>> func = ident + Group(Opt(DelimitedList(term))) + >>> print(func.parse_string("fn a, b, 100")) + ['fn', ['a', 'b', '100']] """ def __init__(self, expr: ParserElement, aslist: bool = False) -> None: @@ -5923,35 +6321,48 @@ class Dict(TokenConverter): The optional ``asdict`` argument when set to True will return the parsed tokens as a Python dict instead of a pyparsing ParseResults. - Example:: + Example: - data_word = Word(alphas) - label = data_word + FollowedBy(':') + .. doctest:: - text = "shape: SQUARE posn: upper left color: light blue texture: burlap" - attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + >>> data_word = Word(alphas) + >>> label = data_word + FollowedBy(':') - # print attributes as plain groups - print(attr_expr[1, ...].parse_string(text).dump()) + >>> attr_expr = ( + ... label + Suppress(':') + ... + OneOrMore(data_word, stop_on=label) + ... .set_parse_action(' '.join) + ... ) - # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names - result = Dict(Group(attr_expr)[1, ...]).parse_string(text) - print(result.dump()) - - # access named fields as dict entries, or output as dict - print(result['shape']) - print(result.as_dict()) - - prints:: + >>> text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + >>> # print attributes as plain groups + >>> print(attr_expr[1, ...].parse_string(text).dump()) ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap'] + + # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) + # Dict will auto-assign names. + >>> result = Dict(Group(attr_expr)[1, ...]).parse_string(text) + >>> print(result.dump()) [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] - color: 'light blue' - posn: 'upper left' - shape: 'SQUARE' - texture: 'burlap' + [0]: + ['shape', 'SQUARE'] + [1]: + ['posn', 'upper left'] + [2]: + ['color', 'light blue'] + [3]: + ['texture', 'burlap'] + + # access named fields as dict entries, or output as dict + >>> print(result['shape']) SQUARE - {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'} + >>> print(result.as_dict()) + {'shape': 'SQUARE', 'posn': 'upper left', 'color': 'light blue', 'texture': 'burlap'} See more examples at :class:`ParseResults` of accessing fields by results name. """ @@ -6004,29 +6415,28 @@ class Dict(TokenConverter): class Suppress(TokenConverter): """Converter for ignoring the results of a parsed expression. - Example:: + Example: - source = "a, b, c,d" - wd = Word(alphas) - wd_list1 = wd + (',' + wd)[...] - print(wd_list1.parse_string(source)) + .. doctest:: + + >>> source = "a, b, c,d" + >>> wd = Word(alphas) + >>> wd_list1 = wd + (',' + wd)[...] + >>> print(wd_list1.parse_string(source)) + ['a', ',', 'b', ',', 'c', ',', 'd'] # often, delimiters that are useful during parsing are just in the # way afterward - use Suppress to keep them out of the parsed output - wd_list2 = wd + (Suppress(',') + wd)[...] - print(wd_list2.parse_string(source)) + >>> wd_list2 = wd + (Suppress(',') + wd)[...] + >>> print(wd_list2.parse_string(source)) + ['a', 'b', 'c', 'd'] # Skipped text (using '...') can be suppressed as well - source = "lead in START relevant text END trailing text" - start_marker = Keyword("START") - end_marker = Keyword("END") - find_body = Suppress(...) + start_marker + ... + end_marker - print(find_body.parse_string(source) - - prints:: - - ['a', ',', 'b', ',', 'c', ',', 'd'] - ['a', 'b', 'c', 'd'] + >>> source = "lead in START relevant text END trailing text" + >>> start_marker = Keyword("START") + >>> end_marker = Keyword("END") + >>> find_body = Suppress(...) + start_marker + ... + end_marker + >>> print(find_body.parse_string(source)) ['START', 'relevant text ', 'END'] (See also :class:`DelimitedList`.) @@ -6056,6 +6466,7 @@ class Suppress(TokenConverter): return self +# XXX: Example needs to be re-done for updated output def trace_parse_action(f: ParseAction) -> ParseAction: """Decorator for debugging parse actions. @@ -6064,7 +6475,18 @@ def trace_parse_action(f: ParseAction) -> ParseAction: When the parse action completes, the decorator will print ``"<<"`` followed by the returned value, or any exception that the parse action raised. - Example:: + Example: + + .. testsetup:: stderr + + import sys + sys.stderr = sys.stdout + + .. testcleanup:: stderr + + sys.stderr = sys.__stderr__ + + .. testcode:: stderr wd = Word(alphas) @@ -6075,11 +6497,18 @@ def trace_parse_action(f: ParseAction) -> ParseAction: wds = wd[1, ...].set_parse_action(remove_duplicate_chars) print(wds.parse_string("slkdjs sld sldd sdlf sdljf")) - prints:: + prints: - >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {})) + .. testoutput:: stderr + :options: +NORMALIZE_WHITESPACE + + >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', + 0, ParseResults(['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {})) <<leaving remove_duplicate_chars (ret: 'dfjkls') ['dfjkls'] + + .. versionchanged:: 3.1.0 + Exception type added to output """ f = _trim_arity(f) @@ -6255,6 +6684,8 @@ quoted_string = Combine( ) ).set_name("quoted string using single or double quotes") +# XXX: Is there some way to make this show up in API docs? +# .. versionadded:: 3.1.0 python_quoted_string = Combine( (Regex(r'"""(?:[^"\\]|""(?!")|"(?!"")|\\.)*', flags=re.MULTILINE) + '"""').set_name( "multiline double quoted string" diff --git a/contrib/python/pyparsing/py3/pyparsing/diagram/__init__.py b/contrib/python/pyparsing/py3/pyparsing/diagram/__init__.py index 526cf3862a4..af1aa47bbca 100644 --- a/contrib/python/pyparsing/py3/pyparsing/diagram/__init__.py +++ b/contrib/python/pyparsing/py3/pyparsing/diagram/__init__.py @@ -112,9 +112,14 @@ T = TypeVar("T") class EachItem(railroad.Group): """ Custom railroad item to compose a: - - Group containing a - - OneOrMore containing a - - Choice of the elements in the Each + + - :class:`railroad.Group` containing a + + - :class:`railroad.OneOrMore` containing a + + - :class:`railroad.Choice` of the elements in the + :class:`railroad.Each` + with the group label indicating that all must be matched """ @@ -152,8 +157,9 @@ class EditablePartial(Generic[T]): @classmethod def from_call(cls, func: Callable[..., T], *args, **kwargs) -> EditablePartial[T]: """ - If you call this function in the same way that you would call the constructor, it will store the arguments - as you expect. For example EditablePartial.from_call(Fraction, 1, 3)() == Fraction(1, 3) + If you call this function in the same way that you would call the constructor, + it will store the arguments as you expect. For example + ``EditablePartial.from_call(Fraction, 1, 3)() == Fraction(1, 3)`` """ return EditablePartial(func=func, args=list(args), kwargs=kwargs) @@ -179,7 +185,9 @@ class EditablePartial(Generic[T]): def railroad_to_html(diagrams: list[NamedDiagram], embed=False, **kwargs) -> str: """ - Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams + Given a list of :class:`NamedDiagram`, produce a single HTML string + that visualises those diagrams. + :params kwargs: kwargs to be passed in to the template """ data = [] @@ -231,16 +239,22 @@ def to_railroad( """ Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram creation if you want to access the Railroad tree before it is converted to HTML + :param element: base element of the parser being diagrammed - :param diagram_kwargs: kwargs to pass to the Diagram() constructor - :param vertical: (optional) - int - limit at which number of alternatives should be - shown vertically instead of horizontally - :param show_results_names - bool to indicate whether results name annotations should be - included in the diagram - :param show_groups - bool to indicate whether groups should be highlighted with an unlabeled - surrounding box - :param show_hidden - bool to indicate whether internal elements that are typically hidden - should be shown + + :param diagram_kwargs: kwargs to pass to the :meth:`Diagram` constructor + + :param vertical: (optional) int - limit at which number of alternatives + should be shown vertically instead of horizontally + + :param show_results_names: bool to indicate whether results name + annotations should be included in the diagram + + :param show_groups: bool to indicate whether groups should be highlighted + with an unlabeled surrounding box + + :param show_hidden: bool to indicate whether internal elements that are + typically hidden should be shown """ # Convert the whole tree underneath the root lookup = ConverterState(diagram_kwargs=diagram_kwargs or {}) diff --git a/contrib/python/pyparsing/py3/pyparsing/exceptions.py b/contrib/python/pyparsing/py3/pyparsing/exceptions.py index fe07a855856..2c62ee357d6 100644 --- a/contrib/python/pyparsing/py3/pyparsing/exceptions.py +++ b/contrib/python/pyparsing/py3/pyparsing/exceptions.py @@ -192,10 +192,20 @@ class ParseBaseException(Exception): return copy.copy(self) def formatted_message(self) -> str: + """ + Output the formatted exception message. + Can be overridden to customize the message formatting or contents. + + .. versionadded:: 3.2.0 + """ found_phrase = f", found {self.found}" if self.found else "" return f"{self.msg}{found_phrase} (at char {self.loc}), (line:{self.lineno}, col:{self.column})" def __str__(self) -> str: + """ + .. versionchanged:: 3.2.0 + Now uses :meth:`formatted_message` to format message. + """ return self.formatted_message() def __repr__(self): @@ -229,7 +239,9 @@ class ParseBaseException(Exception): Returns a multi-line string listing the ParserElements and/or function names in the exception's stack trace. - Example:: + Example: + + .. testcode:: # an expression to parse 3 integers expr = pp.Word(pp.nums) * 3 @@ -239,11 +251,13 @@ class ParseBaseException(Exception): except pp.ParseException as pe: print(pe.explain(depth=0)) - prints:: + prints: + + .. testoutput:: 123 456 A789 ^ - ParseException: Expected W:(0-9), found 'A' (at char 8), (line:1, col:9) + ParseException: Expected W:(0-9), found 'A789' (at char 8), (line:1, col:9) Note: the diagnostic output will include string representations of the expressions that failed to parse. These representations will be more helpful if you use `set_name` to @@ -266,7 +280,9 @@ class ParseException(ParseBaseException): """ Exception thrown when a parse expression doesn't match the input string - Example:: + Example: + + .. testcode:: integer = Word(nums).set_name("integer") try: @@ -274,7 +290,9 @@ class ParseException(ParseBaseException): except ParseException as pe: print(pe, f"column: {pe.column}") - prints:: + prints: + + .. testoutput:: Expected integer, found 'ABC' (at char 0), (line:1, col:1) column: 1 @@ -299,11 +317,12 @@ class ParseSyntaxException(ParseFatalException): class RecursiveGrammarException(Exception): """ + .. deprecated:: 3.0.0 + Only used by the deprecated :meth:`ParserElement.validate`. + Exception thrown by :class:`ParserElement.validate` if the grammar could be left-recursive; parser may need to enable left recursion using :class:`ParserElement.enable_left_recursion<ParserElement.enable_left_recursion>` - - Deprecated: only used by deprecated method ParserElement.validate. """ def __init__(self, parseElementList) -> None: diff --git a/contrib/python/pyparsing/py3/pyparsing/helpers.py b/contrib/python/pyparsing/py3/pyparsing/helpers.py index 2badd68d64f..09697eda156 100644 --- a/contrib/python/pyparsing/py3/pyparsing/helpers.py +++ b/contrib/python/pyparsing/py3/pyparsing/helpers.py @@ -38,27 +38,38 @@ def counted_array( If ``int_expr`` is specified, it should be a pyparsing expression that produces an integer value. - Example:: + Examples: - counted_array(Word(alphas)).parse_string('2 ab cd ef') # -> ['ab', 'cd'] + .. doctest:: - # in this parser, the leading integer value is given in binary, - # '10' indicating that 2 values are in the array - binary_constant = Word('01').set_parse_action(lambda t: int(t[0], 2)) - counted_array(Word(alphas), int_expr=binary_constant).parse_string('10 ab cd ef') # -> ['ab', 'cd'] + >>> counted_array(Word(alphas)).parse_string('2 ab cd ef') + ParseResults(['ab', 'cd'], {}) - # if other fields must be parsed after the count but before the - # list items, give the fields results names and they will - # be preserved in the returned ParseResults: - count_with_metadata = integer + Word(alphas)("type") - typed_array = counted_array(Word(alphanums), int_expr=count_with_metadata)("items") - result = typed_array.parse_string("3 bool True True False") - print(result.dump()) + - In this parser, the leading integer value is given in binary, + '10' indicating that 2 values are in the array: - # prints - # ['True', 'True', 'False'] - # - items: ['True', 'True', 'False'] - # - type: 'bool' + .. doctest:: + + >>> binary_constant = Word('01').set_parse_action(lambda t: int(t[0], 2)) + >>> counted_array(Word(alphas), int_expr=binary_constant + ... ).parse_string('10 ab cd ef') + ParseResults(['ab', 'cd'], {}) + + - If other fields must be parsed after the count but before the + list items, give the fields results names and they will + be preserved in the returned ParseResults: + + .. doctest:: + + >>> ppc = pyparsing.common + >>> count_with_metadata = ppc.integer + Word(alphas)("type") + >>> typed_array = counted_array(Word(alphanums), + ... int_expr=count_with_metadata)("items") + >>> result = typed_array.parse_string("3 bool True True False") + >>> print(result.dump()) + ['True', 'True', 'False'] + - items: ['True', 'True', 'False'] + - type: 'bool' """ intExpr = intExpr or int_expr array_expr = Forward() @@ -84,9 +95,11 @@ def match_previous_literal(expr: ParserElement) -> ParserElement: the tokens matched in a previous expression, that is, it looks for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = match_previous_literal(first) - match_expr = first + ":" + second + .. testcode:: + + first = Word(nums) + second = match_previous_literal(first) + match_expr = first + ":" + second will match ``"1:1"``, but not ``"1:2"``. Because this matches a previous literal, will also match the leading @@ -117,11 +130,13 @@ def match_previous_literal(expr: ParserElement) -> ParserElement: def match_previous_expr(expr: ParserElement) -> ParserElement: """Helper to define an expression that is indirectly defined from the tokens matched in a previous expression, that is, it looks for - a 'repeat' of a previous expression. For example:: + a 'repeat' of a previous expression. For example: + + .. testcode:: - first = Word(nums) - second = match_previous_expr(first) - match_expr = first + ":" + second + first = Word(nums) + second = match_previous_expr(first) + match_expr = first + ":" + second will match ``"1:1"``, but not ``"1:2"``. Because this matches by expressions, will *not* match the leading ``"1:1"`` @@ -164,32 +179,35 @@ def one_of( regardless of the input order, but returns a :class:`MatchFirst` for best performance. - Parameters: + :param strs: a string of space-delimited literals, or a collection of + string literals + :param caseless: treat all literals as caseless + :param use_regex: bool - as an optimization, will + generate a :class:`Regex` object; otherwise, will generate + a :class:`MatchFirst` object (if ``caseless=True`` or + ``as_keyword=True``, or if creating a :class:`Regex` raises an exception) + :param as_keyword: bool - enforce :class:`Keyword`-style matching on the + generated expressions + + Parameters ``asKeyword`` and ``useRegex`` are retained for pre-PEP8 + compatibility, but will be removed in a future release. + + Example: - - ``strs`` - a string of space-delimited literals, or a collection of - string literals - - ``caseless`` - treat all literals as caseless - (default= ``False``) - - ``use_regex`` - as an optimization, will - generate a :class:`Regex` object; otherwise, will generate - a :class:`MatchFirst` object (if ``caseless=True`` or ``as_keyword=True``, or if - creating a :class:`Regex` raises an exception) - (default= ``True``) - - ``as_keyword`` - enforce :class:`Keyword`-style matching on the - generated expressions - (default= ``False``) - - ``asKeyword`` and ``useRegex`` are retained for pre-PEP8 compatibility, - but will be removed in a future release + .. testcode:: - Example:: + comp_oper = one_of("< = > <= >= !=") + var = Word(alphas) + number = Word(nums) + term = var | number + comparison_expr = term + comp_oper + term + print(comparison_expr.search_string("B = 12 AA=23 B<=AA AA>12")) - comp_oper = one_of("< = > <= >= !=") - var = Word(alphas) - number = Word(nums) - term = var | number - comparison_expr = term + comp_oper + term - print(comparison_expr.search_string("B = 12 AA=23 B<=AA AA>12")) + prints: - prints:: + .. testoutput:: - [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] + [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] """ asKeyword = asKeyword or as_keyword useRegex = useRegex and use_regex @@ -254,7 +272,7 @@ def one_of( patt = rf"\b(?:{patt})\b" ret = Regex(patt, flags=re_flags) - ret.set_name(" | ".join(re.escape(s) for s in symbols)) + ret.set_name(" | ".join(repr(s) for s in symbols)) if caseless: # add parse action to return symbols as specified, not in random @@ -293,32 +311,49 @@ def dict_of(key: ParserElement, value: ParserElement) -> Dict: pattern can include named results, so that the :class:`Dict` results can include named token fields. - Example:: + Example: - text = "shape: SQUARE posn: upper left color: light blue texture: burlap" - attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) - print(attr_expr[1, ...].parse_string(text).dump()) + .. doctest:: - attr_label = label - attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join) + >>> text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + + >>> data_word = Word(alphas) + >>> label = data_word + FollowedBy(':') + >>> attr_expr = ( + ... label + ... + Suppress(':') + ... + OneOrMore(data_word, stop_on=label) + ... .set_parse_action(' '.join)) + >>> print(attr_expr[1, ...].parse_string(text).dump()) + ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap'] - # similar to Dict, but simpler call format - result = dict_of(attr_label, attr_value).parse_string(text) - print(result.dump()) - print(result['shape']) - print(result.shape) # object attribute access works too - print(result.as_dict()) + >>> attr_label = label + >>> attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label + ... ).set_parse_action(' '.join) - prints:: + # similar to Dict, but simpler call format + >>> result = dict_of(attr_label, attr_value).parse_string(text) + >>> print(result.dump()) + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] + - color: 'light blue' + - posn: 'upper left' + - shape: 'SQUARE' + - texture: 'burlap' + [0]: + ['shape', 'SQUARE'] + [1]: + ['posn', 'upper left'] + [2]: + ['color', 'light blue'] + [3]: + ['texture', 'burlap'] - [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] - - color: 'light blue' - - posn: 'upper left' - - shape: 'SQUARE' - - texture: 'burlap' - SQUARE - SQUARE - {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} + >>> print(result['shape']) + SQUARE + >>> print(result.shape) # object attribute access works too + SQUARE + >>> print(result.as_dict()) + {'shape': 'SQUARE', 'posn': 'upper left', 'color': 'light blue', 'texture': 'burlap'} """ return Dict(OneOrMore(Group(key + value))) @@ -344,18 +379,22 @@ def original_text_for( The ``asString`` pre-PEP8 argument is retained for compatibility, but will be removed in a future release. - Example:: + Example: - src = "this is test <b> bold <i>text</i> </b> normal text " - for tag in ("b", "i"): - opener, closer = make_html_tags(tag) - patt = original_text_for(opener + ... + closer) - print(patt.search_string(src)[0]) + .. testcode:: - prints:: + src = "this is test <b> bold <i>text</i> </b> normal text " + for tag in ("b", "i"): + opener, closer = make_html_tags(tag) + patt = original_text_for(opener + ... + closer) + print(patt.search_string(src)[0]) - ['<b> bold <i>text</i> </b>'] - ['<i>text</i>'] + prints: + + .. testoutput:: + + ['<b> bold <i>text</i> </b>'] + ['<i>text</i>'] """ asString = asString and as_string @@ -385,7 +424,9 @@ def ungroup(expr: ParserElement) -> ParserElement: def locatedExpr(expr: ParserElement) -> ParserElement: """ - (DEPRECATED - future code should use the :class:`Located` class) + .. deprecated:: 3.0.0 + Use the :class:`Located` class instead. + Helper to decorate a returned token with its starting and ending locations in the input string. @@ -396,19 +437,24 @@ def locatedExpr(expr: ParserElement) -> ParserElement: - ``value`` - the actual parsed results Be careful if the input text contains ``<TAB>`` characters, you - may want to call :class:`ParserElement.parse_with_tabs` + may want to call :meth:`ParserElement.parse_with_tabs` + + Example: + + .. testcode:: - Example:: + wd = Word(alphas) + res = locatedExpr(wd).search_string("ljsdf123lksdjjf123lkkjj1222") + for match in res: + print(match) - wd = Word(alphas) - for match in locatedExpr(wd).search_string("ljsdf123lksdjjf123lkkjj1222"): - print(match) + prints: - prints:: + .. testoutput:: - [[0, 'ljsdf', 5]] - [[8, 'lksdjjf', 15]] - [[18, 'lkkjj', 23]] + [[0, 'ljsdf', 5]] + [[8, 'lksdjjf', 15]] + [[18, 'lkkjj', 23]] """ locator = Empty().set_parse_action(lambda ss, ll, tt: ll) return Group( @@ -427,25 +473,26 @@ def nested_expr( opener: Union[str, ParserElement] = "(", closer: Union[str, ParserElement] = ")", content: typing.Optional[ParserElement] = None, - ignore_expr: ParserElement = _NO_IGNORE_EXPR_GIVEN, + ignore_expr: typing.Optional[ParserElement] = _NO_IGNORE_EXPR_GIVEN, *, - ignoreExpr: ParserElement = _NO_IGNORE_EXPR_GIVEN, + ignoreExpr: typing.Optional[ParserElement] = _NO_IGNORE_EXPR_GIVEN, ) -> ParserElement: """Helper method for defining nested lists enclosed in opening and closing delimiters (``"("`` and ``")"`` are the default). - Parameters: + :param opener: str - opening character for a nested list + (default= ``"("``); can also be a pyparsing expression + + :param closer: str - closing character for a nested list + (default= ``")"``); can also be a pyparsing expression - - ``opener`` - opening character for a nested list - (default= ``"("``); can also be a pyparsing expression - - ``closer`` - closing character for a nested list - (default= ``")"``); can also be a pyparsing expression - - ``content`` - expression for items within the nested lists - (default= ``None``) - - ``ignore_expr`` - expression for ignoring opening and closing delimiters - (default= :class:`quoted_string`) - - ``ignoreExpr`` - this pre-PEP8 argument is retained for compatibility - but will be removed in a future release + :param content: expression for items within the nested lists + + :param ignore_expr: expression for ignoring opening and closing delimiters + (default = :class:`quoted_string`) + + Parameter ``ignoreExpr`` is retained for compatibility + but will be removed in a future release. If an expression is not provided for the content argument, the nested expression will capture all whitespace-delimited content @@ -459,44 +506,48 @@ def nested_expr( :class:`quoted_string`, but if no expressions are to be ignored, then pass ``None`` for this argument. - Example:: + Example: + + .. testcode:: + + data_type = one_of("void int short long char float double") + decl_data_type = Combine(data_type + Opt(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR, RPAR = map(Suppress, "()") - data_type = one_of("void int short long char float double") - decl_data_type = Combine(data_type + Opt(Word('*'))) - ident = Word(alphas+'_', alphanums+'_') - number = pyparsing_common.number - arg = Group(decl_data_type + ident) - LPAR, RPAR = map(Suppress, "()") + code_body = nested_expr('{', '}', ignore_expr=(quoted_string | c_style_comment)) - code_body = nested_expr('{', '}', ignore_expr=(quoted_string | c_style_comment)) + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Opt(DelimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(c_style_comment) - c_function = (decl_data_type("type") - + ident("name") - + LPAR + Opt(DelimitedList(arg), [])("args") + RPAR - + code_body("body")) - c_function.ignore(c_style_comment) + source_code = ''' + int is_odd(int x) { + return (x%2); + } - source_code = ''' - int is_odd(int x) { - return (x%2); - } + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.search_string(source_code): + print(f"{func.name} ({func.type}) args: {func.args}") - int dec_to_hex(char hchar) { - if (hchar >= '0' && hchar <= '9') { - return (ord(hchar)-ord('0')); - } else { - return (10+ord(hchar)-ord('A')); - } - } - ''' - for func in c_function.search_string(source_code): - print("%(name)s (%(type)s) args: %(args)s" % func) + prints: - prints:: + .. testoutput:: - is_odd (int) args: [['int', 'x']] - dec_to_hex (int) args: [['char', 'hchar']] + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] """ if ignoreExpr != ignore_expr: ignoreExpr = ignore_expr if ignoreExpr is _NO_IGNORE_EXPR_GIVEN else ignoreExpr @@ -574,7 +625,8 @@ def nested_expr( def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">")): - """Internal helper to construct opening and closing tag expressions, given a tag name""" + """Internal helper to construct opening and closing tag expressions, + given a tag name""" if isinstance(tagStr, str_type): resname = tagStr tagStr = Keyword(tagStr, caseless=not xml) @@ -638,22 +690,26 @@ def make_html_tags( given a tag name. Matches tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values. - Example:: + Example: + + .. testcode:: + + text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>' + # make_html_tags returns pyparsing expressions for the opening and + # closing tags as a 2-tuple + a, a_end = make_html_tags("A") + link_expr = a + SkipTo(a_end)("link_text") + a_end - text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>' - # make_html_tags returns pyparsing expressions for the opening and - # closing tags as a 2-tuple - a, a_end = make_html_tags("A") - link_expr = a + SkipTo(a_end)("link_text") + a_end + for link in link_expr.search_string(text): + # attributes in the <A> tag (like "href" shown here) are + # also accessible as named results + print(link.link_text, '->', link.href) - for link in link_expr.search_string(text): - # attributes in the <A> tag (like "href" shown here) are - # also accessible as named results - print(link.link_text, '->', link.href) + prints: - prints:: + .. testoutput:: - pyparsing -> https://github.com/pyparsing/pyparsing/wiki + pyparsing -> https://github.com/pyparsing/pyparsing/wiki """ return _makeTags(tag_str, False) @@ -735,69 +791,77 @@ def infix_notation( Parameters: - - ``base_expr`` - expression representing the most basic operand to - be used in the expression - - ``op_list`` - list of tuples, one for each operator precedence level - in the expression grammar; each tuple is of the form ``(op_expr, - num_operands, right_left_assoc, (optional)parse_action)``, where: + :param base_expr: expression representing the most basic operand to + be used in the expression + :param op_list: list of tuples, one for each operator precedence level + in the expression grammar; each tuple is of the form ``(op_expr, + num_operands, right_left_assoc, (optional)parse_action)``, where: + + - ``op_expr`` is the pyparsing expression for the operator; may also + be a string, which will be converted to a Literal; if ``num_operands`` + is 3, ``op_expr`` is a tuple of two expressions, for the two + operators separating the 3 terms + - ``num_operands`` is the number of terms for this operator (must be 1, + 2, or 3) + - ``right_left_assoc`` is the indicator whether the operator is right + or left associative, using the pyparsing-defined constants + ``OpAssoc.RIGHT`` and ``OpAssoc.LEFT``. + - ``parse_action`` is the parse action to be associated with + expressions matching this operator expression (the parse action + tuple member may be omitted); if the parse action is passed + a tuple or list of functions, this is equivalent to calling + ``set_parse_action(*fn)`` + (:class:`ParserElement.set_parse_action`) + + :param lpar: expression for matching left-parentheses; if passed as a + str, then will be parsed as ``Suppress(lpar)``. If lpar is passed as + an expression (such as ``Literal('(')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress('(')``) + :param rpar: expression for matching right-parentheses; if passed as a + str, then will be parsed as ``Suppress(rpar)``. If rpar is passed as + an expression (such as ``Literal(')')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress(')')``) + + Example: + + .. testcode:: - - ``op_expr`` is the pyparsing expression for the operator; may also - be a string, which will be converted to a Literal; if ``num_operands`` - is 3, ``op_expr`` is a tuple of two expressions, for the two - operators separating the 3 terms - - ``num_operands`` is the number of terms for this operator (must be 1, - 2, or 3) - - ``right_left_assoc`` is the indicator whether the operator is right - or left associative, using the pyparsing-defined constants - ``OpAssoc.RIGHT`` and ``OpAssoc.LEFT``. - - ``parse_action`` is the parse action to be associated with - expressions matching this operator expression (the parse action - tuple member may be omitted); if the parse action is passed - a tuple or list of functions, this is equivalent to calling - ``set_parse_action(*fn)`` - (:class:`ParserElement.set_parse_action`) - - ``lpar`` - expression for matching left-parentheses; if passed as a - str, then will be parsed as ``Suppress(lpar)``. If lpar is passed as - an expression (such as ``Literal('(')``), then it will be kept in - the parsed results, and grouped with them. (default= ``Suppress('(')``) - - ``rpar`` - expression for matching right-parentheses; if passed as a - str, then will be parsed as ``Suppress(rpar)``. If rpar is passed as - an expression (such as ``Literal(')')``), then it will be kept in - the parsed results, and grouped with them. (default= ``Suppress(')')``) + # simple example of four-function arithmetic with ints and + # variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier - Example:: + arith_expr = infix_notation(integer | varname, + [ + ('-', 1, OpAssoc.RIGHT), + (one_of('* /'), 2, OpAssoc.LEFT), + (one_of('+ -'), 2, OpAssoc.LEFT), + ]) - # simple example of four-function arithmetic with ints and - # variable names - integer = pyparsing_common.signed_integer - varname = pyparsing_common.identifier + arith_expr.run_tests(''' + 5+3*6 + (5+3)*6 + (5+x)*y + -2--11 + ''', full_dump=False) - arith_expr = infix_notation(integer | varname, - [ - ('-', 1, OpAssoc.RIGHT), - (one_of('* /'), 2, OpAssoc.LEFT), - (one_of('+ -'), 2, OpAssoc.LEFT), - ]) + prints: - arith_expr.run_tests(''' - 5+3*6 - (5+3)*6 - -2--11 - ''', full_dump=False) + .. testoutput:: + :options: +NORMALIZE_WHITESPACE - prints:: - 5+3*6 - [[5, '+', [3, '*', 6]]] + 5+3*6 + [[5, '+', [3, '*', 6]]] - (5+3)*6 - [[[5, '+', 3], '*', 6]] + (5+3)*6 + [[[5, '+', 3], '*', 6]] - (5+x)*y - [[[5, '+', 'x'], '*', 'y']] + (5+x)*y + [[[5, '+', 'x'], '*', 'y']] - -2--11 - [[['-', 2], '-', ['-', 11]]] + -2--11 + [[['-', 2], '-', ['-', 11]]] """ # captive version of FollowedBy that does not do parse actions or capture results names @@ -815,7 +879,7 @@ def infix_notation( if isinstance(rpar, str): rpar = Suppress(rpar) - nested_expr = (lpar + ret + rpar).set_name(f"nested_{base_expr.name}") + nested_expr = (lpar + ret + rpar).set_name(f"nested_{base_expr.name}_expression") # if lpar and rpar are not suppressed, wrap in group if not (isinstance(lpar, Suppress) and isinstance(rpar, Suppress)): @@ -914,89 +978,93 @@ def infix_notation( def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]): """ - (DEPRECATED - use :class:`IndentedBlock` class instead) + .. deprecated:: 3.0.0 + Use the :class:`IndentedBlock` class instead. + Helper method for defining space-delimited indentation blocks, such as those used to define block statements in Python source code. - Parameters: - - - ``blockStatementExpr`` - expression defining syntax of statement that + :param blockStatementExpr: expression defining syntax of statement that is repeated within the indented block - - ``indentStack`` - list created by caller to manage indentation stack + + :param indentStack: list created by caller to manage indentation stack (multiple ``statementWithIndentedBlock`` expressions within a single grammar should share a common ``indentStack``) - - ``indent`` - boolean indicating whether block must be indented beyond + + :param indent: boolean indicating whether block must be indented beyond the current level; set to ``False`` for block of left-most statements - (default= ``True``) A valid block must contain at least one ``blockStatement``. (Note that indentedBlock uses internal parse actions which make it incompatible with packrat parsing.) - Example:: + Example: + + .. testcode:: - data = ''' - def A(z): - A1 - B = 100 - G = A2 - A2 - A3 - B - def BB(a,b,c): - BB1 - def BBA(): - bba1 - bba2 - bba3 - C - D - def spam(x,y): - def eggs(z): - pass - ''' + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + indentStack = [1] + stmt = Forward() - indentStack = [1] - stmt = Forward() + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group("(" + Opt(delimitedList(identifier)) + ")") + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group(funcDecl + func_body) - identifier = Word(alphas, alphanums) - funcDecl = ("def" + identifier + Group("(" + Opt(delimitedList(identifier)) + ")") + ":") - func_body = indentedBlock(stmt, indentStack) - funcDef = Group(funcDecl + func_body) + rvalue = Forward() + funcCall = Group(identifier + "(" + Opt(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << (funcDef | assignment | identifier) - rvalue = Forward() - funcCall = Group(identifier + "(" + Opt(delimitedList(rvalue)) + ")") - rvalue << (funcCall | identifier | Word(nums)) - assignment = Group(identifier + "=" + rvalue) - stmt << (funcDef | assignment | identifier) + module_body = stmt[1, ...] - module_body = stmt[1, ...] + parseTree = module_body.parseString(data) + parseTree.pprint() - parseTree = module_body.parseString(data) - parseTree.pprint() + prints: - prints:: + .. testoutput:: - [['def', - 'A', - ['(', 'z', ')'], - ':', - [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], - 'B', - ['def', - 'BB', - ['(', 'a', 'b', 'c', ')'], - ':', - [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], - 'C', - 'D', - ['def', - 'spam', - ['(', 'x', 'y', ')'], - ':', - [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] """ backup_stacks.append(indentStack[:]) @@ -1096,7 +1164,10 @@ def delimited_list( *, allow_trailing_delim: bool = False, ) -> ParserElement: - """(DEPRECATED - use :class:`DelimitedList` class)""" + """ + .. deprecated:: 3.1.0 + Use the :class:`DelimitedList` class instead. + """ return DelimitedList( expr, delim, combine, min, max, allow_trailing_delim=allow_trailing_delim ) diff --git a/contrib/python/pyparsing/py3/pyparsing/results.py b/contrib/python/pyparsing/py3/pyparsing/results.py index 956230352c8..5dabe58a90d 100644 --- a/contrib/python/pyparsing/py3/pyparsing/results.py +++ b/contrib/python/pyparsing/py3/pyparsing/results.py @@ -1,4 +1,5 @@ # results.py + from __future__ import annotations import collections @@ -44,42 +45,48 @@ class ParseResults: - by list index (``results[0], results[1]``, etc.) - by attribute (``results.<results_name>`` - see :class:`ParserElement.set_results_name`) - Example:: + Example: + + .. testcode:: + + integer = Word(nums) + date_str = (integer.set_results_name("year") + '/' + + integer.set_results_name("month") + '/' + + integer.set_results_name("day")) + # equivalent form: + # date_str = (integer("year") + '/' + # + integer("month") + '/' + # + integer("day")) + + # parse_string returns a ParseResults object + result = date_str.parse_string("1999/12/31") + + def test(s, fn=repr): + print(f"{s} -> {fn(eval(s))}") - integer = Word(nums) - date_str = (integer.set_results_name("year") + '/' - + integer.set_results_name("month") + '/' - + integer.set_results_name("day")) - # equivalent form: - # date_str = (integer("year") + '/' - # + integer("month") + '/' - # + integer("day")) + test("list(result)") + test("result[0]") + test("result['month']") + test("result.day") + test("'month' in result") + test("'minutes' in result") + test("result.dump()", str) - # parse_string returns a ParseResults object - result = date_str.parse_string("1999/12/31") + prints: - def test(s, fn=repr): - print(f"{s} -> {fn(eval(s))}") - test("list(result)") - test("result[0]") - test("result['month']") - test("result.day") - test("'month' in result") - test("'minutes' in result") - test("result.dump()", str) + .. testoutput:: - prints:: + list(result) -> ['1999', '/', '12', '/', '31'] + result[0] -> '1999' + result['month'] -> '12' + result.day -> '31' + 'month' in result -> True + 'minutes' in result -> False + result.dump() -> ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' - list(result) -> ['1999', '/', '12', '/', '31'] - result[0] -> '1999' - result['month'] -> '12' - result.day -> '31' - 'month' in result -> True - 'minutes' in result -> False - result.dump() -> ['1999', '/', '12', '/', '31'] - - day: '31' - - month: '12' - - year: '1999' """ _null_values: tuple[Any, ...] = (None, [], ()) @@ -103,38 +110,59 @@ class ParseResults: class List(list): """ Simple wrapper class to distinguish parsed list results that should be preserved - as actual Python lists, instead of being converted to :class:`ParseResults`:: + as actual Python lists, instead of being converted to :class:`ParseResults`: + + .. testcode:: + + import pyparsing as pp + ppc = pp.common + + LBRACK, RBRACK, LPAR, RPAR = pp.Suppress.using_each("[]()") + element = pp.Forward() + item = ppc.integer + item_list = pp.DelimitedList(element) + element_list = LBRACK + item_list + RBRACK | LPAR + item_list + RPAR + element <<= item | element_list + + # add parse action to convert from ParseResults + # to actual Python collection types + @element_list.add_parse_action + def as_python_list(t): + return pp.ParseResults.List(t.as_list()) + + element.run_tests(''' + 100 + [2,3,4] + [[2, 1],3,4] + [(2, 1),3,4] + (2,3,4) + ([2, 3], 4) + ''', post_parse=lambda s, r: (r[0], type(r[0])) + ) - LBRACK, RBRACK = map(pp.Suppress, "[]") - element = pp.Forward() - item = ppc.integer - element_list = LBRACK + pp.DelimitedList(element) + RBRACK + prints: - # add parse actions to convert from ParseResults to actual Python collection types - def as_python_list(t): - return pp.ParseResults.List(t.as_list()) - element_list.add_parse_action(as_python_list) + .. testoutput:: + :options: +NORMALIZE_WHITESPACE - element <<= item | element_list - element.run_tests(''' - 100 - [2,3,4] - [[2, 1],3,4] - [(2, 1),3,4] - (2,3,4) - ''', post_parse=lambda s, r: (r[0], type(r[0]))) + 100 + (100, <class 'int'>) - prints:: + [2,3,4] + ([2, 3, 4], <class 'list'>) - 100 - (100, <class 'int'>) + [[2, 1],3,4] + ([[2, 1], 3, 4], <class 'list'>) - [2,3,4] - ([2, 3, 4], <class 'list'>) + [(2, 1),3,4] + ([[2, 1], 3, 4], <class 'list'>) - [[2, 1],3,4] - ([[2, 1], 3, 4], <class 'list'>) + (2,3,4) + ([2, 3, 4], <class 'list'>) + + ([2, 3], 4) + ([[2, 3], 4], <class 'list'>) (Used internally by :class:`Group` when `aslist=True`.) """ @@ -301,34 +329,40 @@ class ParseResults: names. A second default return value argument is supported, just as in ``dict.pop()``. - Example:: - - numlist = Word(nums)[...] - print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + Example: - def remove_first(tokens): - tokens.pop(0) - numlist.add_parse_action(remove_first) - print(numlist.parse_string("0 123 321")) # -> ['123', '321'] + .. doctest:: - label = Word(alphas) - patt = label("LABEL") + Word(nums)[1, ...] - print(patt.parse_string("AAB 123 321").dump()) + >>> numlist = Word(nums)[...] + >>> print(numlist.parse_string("0 123 321")) + ['0', '123', '321'] - # Use pop() in a parse action to remove named result (note that corresponding value is not - # removed from list form of results) - def remove_LABEL(tokens): - tokens.pop("LABEL") - return tokens - patt.add_parse_action(remove_LABEL) - print(patt.parse_string("AAB 123 321").dump()) + >>> def remove_first(tokens): + ... tokens.pop(0) + ... + >>> numlist.add_parse_action(remove_first) + [W:(0-9)]... + >>> print(numlist.parse_string("0 123 321")) + ['123', '321'] - prints:: + >>> label = Word(alphas) + >>> patt = label("LABEL") + Word(nums)[1, ...] + >>> print(patt.parse_string("AAB 123 321").dump()) + ['AAB', '123', '321'] + - LABEL: 'AAB' - ['AAB', '123', '321'] - - LABEL: 'AAB' + >>> # Use pop() in a parse action to remove named result + >>> # (note that corresponding value is not + >>> # removed from list form of results) + >>> def remove_LABEL(tokens): + ... tokens.pop("LABEL") + ... return tokens + ... + >>> patt.add_parse_action(remove_LABEL) + {W:(A-Za-z) {W:(0-9)}...} + >>> print(patt.parse_string("AAB 123 321").dump()) + ['AAB', '123', '321'] - ['AAB', '123', '321'] """ if not args: args = [-1] @@ -354,15 +388,20 @@ class ParseResults: Similar to ``dict.get()``. - Example:: + Example: + + .. doctest:: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + >>> integer = Word(nums) + >>> date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + >>> result = date_str.parse_string("1999/12/31") + >>> result.get("year") + '1999' + >>> result.get("hour", "not specified") + 'not specified' + >>> result.get("hour") - result = date_str.parse_string("1999/12/31") - print(result.get("year")) # -> '1999' - print(result.get("hour", "not specified")) # -> 'not specified' - print(result.get("hour")) # -> None """ if key in self: return self[key] @@ -375,16 +414,24 @@ class ParseResults: Similar to ``list.insert()``. - Example:: + Example: + + .. doctest:: - numlist = Word(nums)[...] - print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + >>> numlist = Word(nums)[...] + >>> print(numlist.parse_string("0 123 321")) + ['0', '123', '321'] + + >>> # use a parse action to insert the parse location + >>> # in the front of the parsed results + >>> def insert_locn(locn, tokens): + ... tokens.insert(0, locn) + ... + >>> numlist.add_parse_action(insert_locn) + [W:(0-9)]... + >>> print(numlist.parse_string("0 123 321")) + [0, '0', '123', '321'] - # use a parse action to insert the parse location in the front of the parsed results - def insert_locn(locn, tokens): - tokens.insert(0, locn) - numlist.add_parse_action(insert_locn) - print(numlist.parse_string("0 123 321")) # -> [0, '0', '123', '321'] """ self._toklist.insert(index, ins_string) # fixup indices in token dictionary @@ -398,33 +445,50 @@ class ParseResults: """ Add single element to end of ``ParseResults`` list of elements. - Example:: + Example: + + .. doctest:: - numlist = Word(nums)[...] - print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + >>> numlist = Word(nums)[...] + >>> print(numlist.parse_string("0 123 321")) + ['0', '123', '321'] - # use a parse action to compute the sum of the parsed integers, and add it to the end - def append_sum(tokens): - tokens.append(sum(map(int, tokens))) - numlist.add_parse_action(append_sum) - print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321', 444] + >>> # use a parse action to compute the sum of the parsed integers, + >>> # and add it to the end + >>> def append_sum(tokens): + ... tokens.append(sum(map(int, tokens))) + ... + >>> numlist.add_parse_action(append_sum) + [W:(0-9)]... + >>> print(numlist.parse_string("0 123 321")) + ['0', '123', '321', 444] """ self._toklist.append(item) def extend(self, itemseq): """ - Add sequence of elements to end of ``ParseResults`` list of elements. + Add sequence of elements to end of :class:`ParseResults` list of elements. + + Example: + + .. testcode:: + + patt = Word(alphas)[1, ...] - Example:: + # use a parse action to append the reverse of the matched strings, + # to make a palindrome + def make_palindrome(tokens): + tokens.extend(reversed([t[::-1] for t in tokens])) + return ''.join(tokens) - patt = Word(alphas)[1, ...] + patt.add_parse_action(make_palindrome) + print(patt.parse_string("lskdj sdlkjf lksd")) - # use a parse action to append the reverse of the matched strings, to make a palindrome - def make_palindrome(tokens): - tokens.extend(reversed([t[::-1] for t in tokens])) - return ''.join(tokens) - patt.add_parse_action(make_palindrome) - print(patt.parse_string("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' + prints: + + .. testoutput:: + + ['lskdjsdlkjflksddsklfjkldsjdksl'] """ if isinstance(itemseq, ParseResults): self.__iadd__(itemseq) @@ -510,18 +574,32 @@ class ParseResults: def as_list(self, *, flatten: bool = False) -> list: """ Returns the parse results as a nested list of matching tokens, all converted to strings. - If flatten is True, all the nesting levels in the returned list are collapsed. + If ``flatten`` is True, all the nesting levels in the returned list are collapsed. + + Example: - Example:: + .. doctest:: - patt = Word(alphas)[1, ...] - result = patt.parse_string("sldkj lsdkj sldkj") - # even though the result prints in string-like form, it is actually a pyparsing ParseResults - print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj'] + >>> patt = Word(alphas)[1, ...] + >>> result = patt.parse_string("sldkj lsdkj sldkj") + >>> # even though the result prints in string-like form, + >>> # it is actually a pyparsing ParseResults + >>> type(result) + <class 'pyparsing.results.ParseResults'> + >>> print(result) + ['sldkj', 'lsdkj', 'sldkj'] - # Use as_list() to create an actual list - result_list = result.as_list() - print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj'] + .. doctest:: + + >>> # Use as_list() to create an actual list + >>> result_list = result.as_list() + >>> type(result_list) + <class 'list'> + >>> print(result_list) + ['sldkj', 'lsdkj', 'sldkj'] + + .. versionchanged:: 3.2.0 + New ``flatten`` argument. """ def flattened(pr): @@ -545,21 +623,33 @@ class ParseResults: """ Returns the named parse results as a nested dictionary. - Example:: + Example: + + .. doctest:: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + >>> integer = pp.Word(pp.nums) + >>> date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - result = date_str.parse_string('12/31/1999') - print(type(result), repr(result)) # -> <class 'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) + >>> result = date_str.parse_string('1999/12/31') + >>> type(result) + <class 'pyparsing.results.ParseResults'> + >>> result + ParseResults(['1999', '/', '12', '/', '31'], {'year': '1999', 'month': '12', 'day': '31'}) - result_dict = result.as_dict() - print(type(result_dict), repr(result_dict)) # -> <class 'dict'> {'day': '1999', 'year': '12', 'month': '31'} + >>> result_dict = result.as_dict() + >>> type(result_dict) + <class 'dict'> + >>> result_dict + {'year': '1999', 'month': '12', 'day': '31'} - # even though a ParseResults supports dict-like access, sometime you just need to have a dict - import json - print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable - print(json.dumps(result.as_dict())) # -> {"month": "31", "day": "1999", "year": "12"} + >>> # even though a ParseResults supports dict-like access, + >>> # sometime you just need to have a dict + >>> import json + >>> print(json.dumps(result)) + Traceback (most recent call last): + TypeError: Object of type ParseResults is not JSON serializable + >>> print(json.dumps(result.as_dict())) + {"year": "1999", "month": "12", "day": "31"} """ def to_item(obj): @@ -572,10 +662,10 @@ class ParseResults: def copy(self) -> ParseResults: """ - Returns a new shallow copy of a :class:`ParseResults` object. `ParseResults` - items contained within the source are shared with the copy. Use - :class:`ParseResults.deepcopy()` to create a copy with its own separate - content values. + Returns a new shallow copy of a :class:`ParseResults` object. + :class:`ParseResults` items contained within the source are + shared with the copy. Use :meth:`ParseResults.deepcopy` to + create a copy with its own separate content values. """ ret = ParseResults(self._toklist) ret._tokdict = self._tokdict.copy() @@ -587,6 +677,8 @@ class ParseResults: def deepcopy(self) -> ParseResults: """ Returns a new deep copy of a :class:`ParseResults` object. + + .. versionadded:: 3.1.0 """ ret = self.copy() # replace values with copies if they are of known mutable types @@ -607,28 +699,35 @@ class ParseResults: def get_name(self) -> str | None: r""" - Returns the results name for this token expression. Useful when several - different expressions might match at a particular location. + Returns the results name for this token expression. + + Useful when several different expressions might match + at a particular location. + + Example: + + .. testcode:: + + integer = Word(nums) + ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") + house_number_expr = Suppress('#') + Word(nums, alphanums) + user_data = (Group(house_number_expr)("house_number") + | Group(ssn_expr)("ssn") + | Group(integer)("age")) + user_info = user_data[1, ...] - Example:: + result = user_info.parse_string("22 111-22-3333 #221B") + for item in result: + print(item.get_name(), ':', item[0]) - integer = Word(nums) - ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") - house_number_expr = Suppress('#') + Word(nums, alphanums) - user_data = (Group(house_number_expr)("house_number") - | Group(ssn_expr)("ssn") - | Group(integer)("age")) - user_info = user_data[1, ...] + prints: - result = user_info.parse_string("22 111-22-3333 #221B") - for item in result: - print(item.get_name(), ':', item[0]) + .. testoutput:: - prints:: + age : 22 + ssn : 111-22-3333 + house_number : 221B - age : 22 - ssn : 111-22-3333 - house_number : 221B """ if self._name: return self._name @@ -659,20 +758,24 @@ class ParseResults: a :class:`ParseResults`. Accepts an optional ``indent`` argument so that this string can be embedded in a nested display of other data. - Example:: + Example: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + .. testcode:: - result = date_str.parse_string('1999/12/31') - print(result.dump()) + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - prints:: + result = date_str.parse_string('1999/12/31') + print(result.dump()) - ['1999', '/', '12', '/', '31'] - - day: '31' - - month: '12' - - year: '1999' + prints: + + .. testoutput:: + + ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' """ out = [] NL = "\n" @@ -734,23 +837,27 @@ class ParseResults: Accepts additional positional or keyword args as defined for `pprint.pprint <https://docs.python.org/3/library/pprint.html#pprint.pprint>`_ . - Example:: + Example: + + .. testcode:: - ident = Word(alphas, alphanums) - num = Word(nums) - func = Forward() - term = ident | num | Group('(' + func + ')') - func <<= ident + Group(Optional(DelimitedList(term))) - result = func.parse_string("fna a,b,(fnb c,d,200),100") - result.pprint(width=40) + ident = Word(alphas, alphanums) + num = Word(nums) + func = Forward() + term = ident | num | Group('(' + func + ')') + func <<= ident + Group(Optional(DelimitedList(term))) + result = func.parse_string("fna a,b,(fnb c,d,200),100") + result.pprint(width=40) - prints:: + prints: - ['fna', - ['a', - 'b', - ['(', 'fnb', ['c', 'd', '200'], ')'], - '100']] + .. testoutput:: + + ['fna', + ['a', + 'b', + ['(', 'fnb', ['c', 'd', '200'], ')'], + '100']] """ pprint.pprint(self.as_list(), *args, **kwargs) @@ -780,9 +887,9 @@ class ParseResults: @classmethod def from_dict(cls, other, name=None) -> ParseResults: """ - Helper classmethod to construct a ``ParseResults`` from a ``dict``, preserving the + Helper classmethod to construct a :class:`ParseResults` from a ``dict``, preserving the name-value relations as results names. If an optional ``name`` argument is - given, a nested ``ParseResults`` will be returned. + given, a nested :class:`ParseResults` will be returned. """ def is_iterable(obj): @@ -805,11 +912,20 @@ class ParseResults: return ret asList = as_list - """Deprecated - use :class:`as_list`""" + """ + .. deprecated:: 3.0.0 + use :meth:`as_list` + """ asDict = as_dict - """Deprecated - use :class:`as_dict`""" + """ + .. deprecated:: 3.0.0 + use :meth:`as_dict` + """ getName = get_name - """Deprecated - use :class:`get_name`""" + """ + .. deprecated:: 3.0.0 + use :meth:`get_name` + """ MutableMapping.register(ParseResults) diff --git a/contrib/python/pyparsing/py3/pyparsing/testing.py b/contrib/python/pyparsing/py3/pyparsing/testing.py index 836b2f86fbe..7def5d37b87 100644 --- a/contrib/python/pyparsing/py3/pyparsing/testing.py +++ b/contrib/python/pyparsing/py3/pyparsing/testing.py @@ -24,24 +24,37 @@ class pyparsing_test: Context manager to be used when writing unit tests that modify pyparsing config values: - packrat parsing - bounded recursion parsing - - default whitespace characters. + - default whitespace characters - default keyword characters - literal string auto-conversion class - - __diag__ settings + - ``__diag__`` settings - Example:: + Example: - with reset_pyparsing_context(): - # test that literals used to construct a grammar are automatically suppressed - ParserElement.inlineLiteralsUsing(Suppress) + .. testcode:: - term = Word(alphas) | Word(nums) - group = Group('(' + term[...] + ')') + ppt = pyparsing.pyparsing_test - # assert that the '()' characters are not included in the parsed tokens - self.assertParseAndCheckList(group, "(abc 123 def)", ['abc', '123', 'def']) + class MyTestClass(ppt.TestParseResultsAsserts): + def test_literal(self): + with ppt.reset_pyparsing_context(): + # test that literals used to construct + # a grammar are automatically suppressed + ParserElement.inline_literals_using(Suppress) - # after exiting context manager, literals are converted to Literal expressions again + term = Word(alphas) | Word(nums) + group = Group('(' + term[...] + ')') + + # assert that the '()' characters + # are not included in the parsed tokens + self.assertParseAndCheckList( + group, + "(abc 123 def)", + ['abc', '123', 'def'] + ) + + # after exiting context manager, literals + # are converted to Literal expressions again """ def __init__(self): @@ -145,7 +158,7 @@ class pyparsing_test: ): """ Convenience wrapper assert to test a parser element and input string, and assert that - the resulting ``ParseResults.asList()`` is equal to the ``expected_list``. + the resulting :meth:`ParseResults.as_list` is equal to the ``expected_list``. """ result = expr.parse_string(test_string, parse_all=True) if verbose: @@ -159,7 +172,7 @@ class pyparsing_test: ): """ Convenience wrapper assert to test a parser element and input string, and assert that - the resulting ``ParseResults.asDict()`` is equal to the ``expected_dict``. + the resulting :meth:`ParseResults.as_dict` is equal to the ``expected_dict``. """ result = expr.parse_string(test_string, parseAll=True) if verbose: @@ -172,13 +185,20 @@ class pyparsing_test: self, run_tests_report, expected_parse_results=None, msg=None ): """ - Unit test assertion to evaluate output of ``ParserElement.runTests()``. If a list of - list-dict tuples is given as the ``expected_parse_results`` argument, then these are zipped - with the report tuples returned by ``runTests`` and evaluated using ``assertParseResultsEquals``. - Finally, asserts that the overall ``runTests()`` success value is ``True``. + Unit test assertion to evaluate output of + :meth:`~ParserElement.run_tests`. + + If a list of list-dict tuples is given as the + ``expected_parse_results`` argument, then these are zipped + with the report tuples returned by ``run_tests()`` + and evaluated using :meth:`assertParseResultsEquals`. + Finally, asserts that the overall + `:meth:~ParserElement.run_tests` success value is ``True``. - :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests - :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] + :param run_tests_report: the return value from :meth:`ParserElement.run_tests` + :type run_tests_report: tuple[bool, list[tuple[str, ParseResults | Exception]]] + :param expected_parse_results: (optional) + :type expected_parse_results: list[tuple[str | list | dict | Exception, ...]] """ run_test_success, run_test_results = run_tests_report @@ -266,23 +286,29 @@ class pyparsing_test: (Line and column numbers are 1-based by default - if debugging a parse action, pass base_1=False, to correspond to the loc value passed to the parse action.) - :param s: tuple(bool, str - string to be printed with line and column numbers - :param start_line: int - (optional) starting line number in s to print (default=1) - :param end_line: int - (optional) ending line number in s to print (default=len(s)) - :param expand_tabs: bool - (optional) expand tabs to spaces, to match the pyparsing default - :param eol_mark: str - (optional) string to mark the end of lines, helps visualize trailing spaces (default="|") - :param mark_spaces: str - (optional) special character to display in place of spaces - :param mark_control: str - (optional) convert non-printing control characters to a placeholding - character; valid values: - - "unicode" - replaces control chars with Unicode symbols, such as "␍" and "␊" - - any single character string - replace control characters with given string - - None (default) - string is displayed as-is - :param indent: str | int - (optional) string to indent with line and column numbers; if an int - is passed, converted to " " * indent - :param base_1: bool - (optional) whether to label string using base 1; if False, string will be - labeled based at 0 (default=True) + :param s: string to be printed with line and column numbers + :param start_line: starting line number in s to print (default=1) + :param end_line: ending line number in s to print (default=len(s)) + :param expand_tabs: expand tabs to spaces, to match the pyparsing default + :param eol_mark: string to mark the end of lines, helps visualize trailing spaces + :param mark_spaces: special character to display in place of spaces + :param mark_control: convert non-printing control characters to a placeholding + character; valid values: + + - ``"unicode"`` - replaces control chars with Unicode symbols, such as "␍" and "␊" + - any single character string - replace control characters with given string + - ``None`` (default) - string is displayed as-is + + + :param indent: string to indent with line and column numbers; if an int + is passed, converted to ``" " * indent`` + :param base_1: whether to label string using base 1; if False, string will be + labeled based at 0 + + :returns: input string with leading line numbers and column number headers - :return: str - input string with leading line numbers and column number headers + .. versionchanged:: 3.2.0 + New ``indent`` and ``base_1`` arguments. """ if expand_tabs: s = s.expandtabs() diff --git a/contrib/python/pyparsing/py3/pyparsing/util.py b/contrib/python/pyparsing/py3/pyparsing/util.py index 1cb16e2e620..7909240bbd1 100644 --- a/contrib/python/pyparsing/py3/pyparsing/util.py +++ b/contrib/python/pyparsing/py3/pyparsing/util.py @@ -45,7 +45,7 @@ def col(loc: int, strg: str) -> int: Note: the default parsing behavior is to expand tabs in the input string before starting the parsing process. See - :class:`ParserElement.parse_string` for more + :meth:`ParserElement.parse_string` for more information on parsing strings containing ``<TAB>`` s, and suggested methods to maintain a consistent view of the parsed string, the parse location, and line and column positions within the parsed string. @@ -60,7 +60,7 @@ def lineno(loc: int, strg: str) -> int: The first line is number 1. Note - the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See :class:`ParserElement.parse_string` + before starting the parsing process. See :meth:`ParserElement.parse_string` for more information on parsing strings containing ``<TAB>`` s, and suggested methods to maintain a consistent view of the parsed string, the parse location, and line and column positions within the parsed string. @@ -186,12 +186,24 @@ class _GroupConsecutive: """ Used as a callable `key` for itertools.groupby to group characters that are consecutive: - itertools.groupby("abcdejkmpqrs", key=IsConsecutive()) - yields: - (0, iter(['a', 'b', 'c', 'd', 'e'])) - (1, iter(['j', 'k'])) - (2, iter(['m'])) - (3, iter(['p', 'q', 'r', 's'])) + + .. testcode:: + + from itertools import groupby + from pyparsing.util import _GroupConsecutive + + grouped = groupby("abcdejkmpqrs", key=_GroupConsecutive()) + for index, group in grouped: + print(tuple([index, list(group)])) + + prints: + + .. testoutput:: + + (0, ['a', 'b', 'c', 'd', 'e']) + (1, ['j', 'k']) + (2, ['m']) + (3, ['p', 'q', 'r', 's']) """ def __init__(self) -> None: @@ -214,21 +226,28 @@ def _collapse_string_to_ranges( Take a string or list of single-character strings, and return a string of the consecutive characters in that string collapsed into groups, as might be used in a regular expression '[a-z]' - character set: + character set:: + 'a' -> 'a' -> '[a]' 'bc' -> 'bc' -> '[bc]' 'defgh' -> 'd-h' -> '[d-h]' 'fdgeh' -> 'd-h' -> '[d-h]' 'jklnpqrtu' -> 'j-lnp-rtu' -> '[j-lnp-rtu]' - Duplicates get collapsed out: + + Duplicates get collapsed out:: + 'aaa' -> 'a' -> '[a]' 'bcbccb' -> 'bc' -> '[bc]' 'defghhgf' -> 'd-h' -> '[d-h]' 'jklnpqrjjjtu' -> 'j-lnp-rtu' -> '[j-lnp-rtu]' - Spaces are preserved: + + Spaces are preserved:: + 'ab c' -> ' a-c' -> '[ a-c]' + Characters that are significant when defining regex ranges - get escaped: + get escaped:: + 'acde[]-' -> r'\-\[\]ac-e' -> r'[\-\[\]ac-e]' """ @@ -425,7 +444,10 @@ def replaced_by_pep8(compat_name: str, fn: C) -> C: # ) return fn(*args, **kwargs) - _inner.__doc__ = f"""Deprecated - use :class:`{fn.__name__}`""" + _inner.__doc__ = f""" + .. deprecated:: 3.0.0 + Use :class:`{fn.__name__}` instead + """ _inner.__name__ = compat_name _inner.__annotations__ = fn.__annotations__ if isinstance(fn, types.FunctionType): diff --git a/contrib/python/pyparsing/py3/ya.make b/contrib/python/pyparsing/py3/ya.make index 13dd585e164..2d439e9d7f9 100644 --- a/contrib/python/pyparsing/py3/ya.make +++ b/contrib/python/pyparsing/py3/ya.make @@ -4,7 +4,7 @@ PY3_LIBRARY() PROVIDES(pyparsing) -VERSION(3.2.3) +VERSION(3.2.5) LICENSE(MIT) |
